前言
今日早读文章由@华尔街见闻技术团队分享。
本文由 @ 池盛星推荐
正文从这开始~
先说说为什么要写这篇文章, 最初的原因是组里的小朋友们看了webpack文档后, 表情都是这样的: (摘自webpack一篇文档的评论区)
和这样的:
是的, 即使是外国佬也在吐槽这文档不是人能看的. 回想起当年自己啃webpack文档的血与泪的往事, 觉得有必要整一个教程, 可以让大家看完后愉悦地搭建起一个webpack打包方案的项目。
可能会有人问webpack到底有什么用, 你不能上来就糊我一脸代码让我马上搞, 我照着搞了一遍结果根本没什么naizi用, 都是骗人的. 所以, 在说webpack之前, 我想先谈一下前端打包方案这几年的演进历程, 在什么场景下, 我们遇到了什么问题, 催生出了应对这些问题的工具. 了解了需求和目的之后, 你就知道什么时候webpack可以帮到你. 我希望我用完之后很爽, 你们用完之后也是。
先说说前端打包方案的黑暗历史
在很长的一段前端历史里, 是不存在打包这个说法的. 那个时候页面基本是纯静态的或者服务端输出的, 没有AJAX, 也没有jQuery. 那个时候的JavaScript就像个玩具, 用处大概就是在侧栏弄个时钟, 用media player放个mp3之类的脚本, 代码量不是很多, 直接放在
AMD规范定义和引用模块的语法太麻烦, 上面介绍的AMD语法仅是最简单通用的语法, API文档里面还有很多变异的写法, 特别是当发生循环引用的时候(a依赖b, b依赖a), 需要使用其他的语法解决这个问题. 而且npm上很多前后端通用的库都是CommonJS的语法. 后来很多人又开始尝试使用ES6模块规范, 如何引用ES6模块又是一个大问题.
项目的文件结构不合理, 因为grunt/gulp是按照文件格式批量处理的, 所以一般会把js, html, css, 图片分别放在不同的目录下, 所以同一个模块的文件会散落在不同的目录下, 开发的时候找文件是个麻烦的事情. code review时想知道一个文件是哪个模块的也很麻烦, 解决办法比如又要在imgs目录下建立按模块命名的文件夹, 里面再放图片.
到了这里, 我们的主角webpack登场了(2012年)(此处应有掌声).
和webpack差不多同期登场的还有Browserify. 这里简单介绍一下Browserify, Browserify的目的是让前端也能用CommonJS的语法require('module')来加载js. 它会从入口js文件开始, 把所有的require()调用的文件打包合并到一个文件, 这样就解决了异步加载的问题. 那么Browserify有什么不足之处导致我不推荐使用它呢? 主要原因有下面几点:
最主要的一点, Browserify不支持把代码打包成多个文件, 在有需要的时候加载. 这就意味着访问任何一个页面都会全量加载所有文件.
Browserify对其他非js文件的加载不够完善, 因为它主要解决的是require()js模块的问题, 其他文件不是它关心的部分. 比如html文件里的img标签, 它只能转成Data URI的形式, 而不能替换为打包后的路径.
因为上面一点Browserify对资源文件的加载支持不够完善, 导致打包时一般都要配合gulp或grunt一块使用, 无谓地增加了打包的难度.
Browserify只支持CommonJS模块规范, 不支持AMD和ES6模块规范, 这意味旧的AMD模块和将来的ES6模块不能使用.
基于以上几点, Browserify并不是一个理想的选择. 那么webpack是否解决了以上的几个问题呢? 废话, 不然介绍它干嘛. 那么下面章节我们用实战的方式来说明webpack是怎么解决上述的问题的.
上手先搞一个简单的SPA应用
一上来步子太大容易扯到蛋, 让我们先弄个最简单的webpack配置来热一下身.
安装Node.js
webpack是基于我大Node.js的打包工具, 上来第一件事自然是先安装Node.js了,传送门->.
初始化一个项目
我们先随便找个地方, 建一个文件夹叫simple, 然后在这里面搭项目. 完成品在examples/simple目录, 大家搞的时候可以参照一下. 我们先看一下目录结构:
├── dist 打包输出目录, 只需部署这个目录到生产环境
├── package.json 项目配置信息
├── node_modules npm安装的依赖包都在这里面
├── src 我们的源代码
│ ├── components 可以复用的模块放在这里面
│ ├── index.html 入口html
│ ├── index.js 入口js
│ ├── libs 不在npm和git上的库扔这里
│ └── views 页面放这里
└── webpack.config.js webpack 配置文件
打开命令行窗口, cd到刚才建的simple目录. 然后执行这个命令初始化项目:
npm init
命令行会要你输入一些配置信息, 我们这里一路按回车下去, 生成一个默认的项目配置文件package.json.
给项目加上语法报错和代码规范检查
我们安装eslint, 用来检查语法报错, 当我们书写js时, 有错误的地方会出现提示 .
npm install eslint eslint-config-enough eslint-loader --save-dev
npm install可以一条命令同时安装多个包, 包之间用空格分隔. 包会被安装进node_modules目录中.
--save-dev会把安装的包和版本号记录到package.json中的devDependencies对象中, 还有一个--save, 会记录到dependencies对象中, 它们的区别, 我们可以先简单的理解为打包工具和测试工具用到的包使用--save-dev存到devDependencies, 比如eslint, webpack. 浏览器中执行的js用到的包存到dependencies, 比如jQuery等. 那么它们用来干嘛的?
因为有些npm包安装是需要编译的, 那么导致windows/mac/linux上编译出的可执行文件是不同的, 也就是无法通用, 因此我们在提交代码到git上去的时候, 一般都会在.gitignore里指定忽略node_modules目录和里面的文件, 这样其他人从git上拉下来的项目是没有node_modules目录的, 这时我们需要运行
npm install
它会读取package.json中的devDependencies和dependencies字段, 把记录的包的相应版本下载下来.
这里eslint-config-enough是配置文件, 它规定了代码规范, 要使它生效, 我们要在package.json中添加内容:
{
"eslintConfig": {
"extends": "enough",
"env": {
"browser": true,
"node": true
}
}
}
业界最有名的语法规范是airbnb出品的, 但它规定的太死板了, 比如不允许使用for-of和for-in等. 感兴趣的同学可以参照这里安装使用.
eslint-loader用于在webpack编译的时候检查代码, 如果有错误, webpack会报错.
项目里安装了eslint还没用, 我们的IDE和编辑器也得要装eslint插件支持它.
Visual Studio Code需要安装ESLint扩展
atom需要安装linter和linter-eslint这两个插件, 装好后重启生效.
WebStorm需要在设置中打开eslint 开关:

写几个页面
我们写一个最简单的SPA应用来介绍SPA应用的内部工作原理. 首先, 建立src/index.html文件, 内容如下:
DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
head>
<body>
body>
html>
它是一个空白页面, 注意这里我们不需要自己写
, 因为打包后的文件名和路径可能会变, 所以我们用webpack插件帮我们自动加上.
然后重点是src/index.js:
import g from './global'
import foo from './views/foo'
import bar from './views/bar'
const routes = {
'/foo': foo,
'/bar': bar
}
class Router {
start() {
window.addEventListener
('popstate', () => {
this.load(location.pathname)
})
this.load(location.pathname)
}
go(path) {
history.pushState({}, '', path)
this.load(path)
}
load(path) {
const view = new routes[path]()
view.mount(document.body)
}
}
g.router = new Router()
g.router.start()
现在我们还没有讲webpack配置所以页面还无法访问, 我们先从理论上讲解一下, 等会弄好webpack配置后再实际看页面效果. 当我们访问http://localhost:8100/foo的时候, 路由会加载 ./views/foo/index.js文件, 我们来看看这个文件:
import g from '../../global'
import template from './index.html'