专栏名称: 前端早读课
我们关注前端,产品体验设计,更关注前端同行的成长。 每天清晨五点早读,四万+同行相伴成长。
目录
相关文章推荐
51好读  ›  专栏  ›  前端早读课

【第992期】webpack 2 打包实战(上)

前端早读课  · 公众号  · 前端  · 2017-07-11 04:50

正文

请到「今天看啥」查看全文


前言

今日早读文章由@华尔街见闻技术团队分享。

本文由 @ 池盛星推荐

正文从这开始~

先说说为什么要写这篇文章, 最初的原因是组里的小朋友们看了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:

    // 引入作为全局对象储存空间的global.js, js文件可以省略后缀

    import g from './global'

    // 引入页面文件
    import foo from './views/foo'
    import bar from './views/bar'

    const routes = {
     '/foo': foo,
     '/bar': bar
    }

    // Router类, 用来控制页面根据当前URL切换
    class Router {
     start() {
       // 点击浏览器后退/前进按钮时会触发window.onpopstate事件, 我们在这时切换到相应页面
       // https://developer.mozilla.org/en-US/docs/Web/Events/popstate
       window.addEventListener ('popstate', () => {
         this.load(location.pathname)
       })

       // 打开页面时加载当前页面
       this.load(location.pathname)
     }

     // 前往path, 会变更地址栏URL, 并加载相应页面
     go(path) {
       // 变更地址栏URL
       history.pushState({}, '', path)
       // 加载页面
       this.load(path)
     }

     // 加载path路径的页面
     load(path) {
       // 创建页面实例
       const view = new routes[path]()
       // 调用页面方法, 把页面加载到document.body中
       view.mount(document.body)
     }
    }

    // new一个路由对象, 赋值为g.router, 这样我们在其他js文件中可以引用到
    g.router = new Router()
    // 启动
    g.router.start()

    现在我们还没有讲webpack配置所以页面还无法访问, 我们先从理论上讲解一下, 等会弄好webpack配置后再实际看页面效果. 当我们访问http://localhost:8100/foo的时候, 路由会加载 ./views/foo/index.js文件, 我们来看看这个文件:

    // 引入全局对象
    import g from '../../global'

    // 引入html模板, 会被作为字符串引入
    import template from './index.html'

    // 引入css, 会生成







    请到「今天看啥」查看全文