webpack是个啥

webpack是个啥?简单说,它是个模块打包机,压缩你的代码,以供浏览器解析。

安装

要安装 Webpack 到本项目,可按照你的需要选择以下任意命令运行:

npm i -D 是 npm install --save-dev 的简写,是指安装模块并保存到 package.json 的 devDependencies。

安装最新稳定版
npm i -D webpack

安装指定版本
npm i -D webpack@<version>

安装最新体验版本
npm i -D webpack@beta

安装到全局
npm i -g webpack

使用

准备三个文件:index.html ,main.jsshow.js,分别是:入口文件,主文件和逻辑文件。

# index.html

<html>
<head>
  <meta charset="UTF-8">
</head>
<body>
<div id="app"></div>
<!--导入 Webpack 输出的 JavaScript 文件-->
<script src="./dist/bundle.js"></script>
</body>
</html>

引入的bundle是什么待会说,接着写show.js

# show.js

// 操作 DOM 元素,把 content 显示到网页上
function show(content) {
  window.document.getElementById('app').innerText = 'Hello,' + content;
}

// 通过 CommonJS 规范导出 show 函数
module.exports = show;

最后写main.js

# main.js

// 通过 CommonJS 规范导入 show 函数
const show = require('./show.js');
// 执行 show 函数
show('Webpack');

三个文件的工作内容如上,一个div,一个innerHTML,一个require引用,然后命令行执行webpack,你会发现多了一个dist文件夹,里面是bundle.js,现在浏览器打开index.html,发现有一句话:Hello,Webpack。

其实bundle.js就是包含了mainshow模块及内置的webpackBootStrap启动函数。

loader入门

main.js里面添加下面的代码:

require('./main.css');

你会发现有一句报错,大致如下:

ERROR in ./main.css 1:0
Module parse failed: Unexpected character '#' (1:0)
You may need an appropriate loader to handle this file type.#app{
|     text-align: center
| }
 @ ./main.js 4:0-21

原因是因为webpack不支持原生解析css文件,如果想支持非js文件,就需要使用webpack的loader机制,现在做一件事情,改一下weback.config.js

module: {
    rules:[
        {
            // 用正则去匹配要用该 loader 转换的 CSS 文件
            test:/\.css$/,
             use: ['style-loader', 'css-loader?minimize'],
        }
    ]
}

现在就告诉了webpack,对于正则匹配到的css文件,用style-loader翻译一下,再交给style-loader把css内容注入到js里面。

配置的rules,需要注意一些问题:

  • use属性的值是一个由Loader组成的数组,Loader的执行顺序是由后向前。
  • 每一个Loader都可以通过URL querystring的方式传入参数,例如上面的css-loader?minimize就是告诉css-loader开启css压缩。

当然Loader接收的参数有很多,详细介绍点击这里

现在解决一下报错问题,重新执行webpack命令之前执行下面命令:npm i -D style-loader css-loader

安装成功后执行webpack,你会发现bundle.js更新了。文件加入了样式,但是没有生成css文件。刷新index.html,文字居中。

Loader传参除了querystring,还有对象的方式,以上Loader配置可以写成这样:

use: [
  'style-loader', 
  {
    loader:'css-loader',
    options:{
      minimize:true,
    }
  }
]

如果你不想配置Loader,也可以通过引入文件的方式,在源码中制定用什么Loader去处理文件,比如:

require('style-loader!css-loader?minimize!./main.css')

plugin入门

看名字就知道是用来开发webpack的额外功能的插件(通过在构建流程中注入钩子)。 该例子会把注入到bundle.js的css提取出来。 配置文件修改如下:

const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
  // JavaScript 执行入口文件
  entry: './main.js',
  output: {
    // 把所有依赖的模块合并输出到一个 bundle.js 文件
    filename: 'bundle.js',
    // 把输出文件都放到 dist 目录下
    path: path.resolve(__dirname, './dist'),
  },
  module: {
    rules: [
      {
        // 用正则去匹配要用该 loader 转换的 CSS 文件
        test: /\.css$/,
        use: ExtractTextPlugin.extract({
          // 转换 .css 文件需要使用的 Loader
          use: ['css-loader'],
        }),
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin({
      // 从 .js 文件中提取出来的 .css 文件的名称
      filename: `[name]_[contenthash:8].css`,
    }),
  ]
};

先安装一个插件:npm i -D extract-text-webpack-plugin 安装成功后会发现dist多了个文件:main_1a87a56a.css,bundle.js没有css代码了!然后把该css文件引入到index.html中就完成了。 由此可见,webpack是通过plugins属性来配置需要使用的插件列表的。plugins是一个数组,每一项都是一个插件的实例。

例如 ExtractTextPlugin 插件的作用是提取出 JavaScript 代码里的 CSS 到一个单独的文件。 对此你可以通过插件的 filename 属性,告诉插件输出的 CSS 文件名称是通过 [name]_[contenthash:8].css 字符串模版生成的,里面的 [name] 代表文件名称, [contenthash:8] 代表根据文件内容算出的8位 hash 值, 还有很多配置选项可以在 ExtractTextPlugin 的主页上查到。

devServer入门

前面的几节只是让 Webpack 正常运行起来了,但在实际开发中你可能会需要:

  1. 提供 HTTP 服务而不是使用本地文件预览;
  2. 监听文件的变化并自动刷新网页,做到实时预览;
  3. 支持 Source Map,以方便调试。

对于这些, Webpack 都为你考虑好了。Webpack 原生支持上述第2、3点内容,再结合官方提供的开发工具 DevServer 也可以很方便地做到第1点。 DevServer会启动一个 HTTP 服务器用于服务网页请求,同时会帮助启动 Webpack ,并接收 Webpack 发出的文件更变信号,通过 WebSocket 协议自动刷新网页做到实时预览。下面安装一个DerServer:

npm i -D webpack-dev-server

安装成功后,我们换个执行命令webpack-dev-server,会看到命令行有个这东西:

Project is running at http://localhost:8080/
webpack output is served from /

现在,DevServer监听着8080端口,DevServer启动后,会一直留在后台, 访问该路径就是访问index.html,现在打开该页面,哇,404了。原因是因为./dist/bundle.js找不到了。这是因为DevServer会把webpack构建出来的文件保存在内存中,想访问必须使用HTTP服务,但是因为DevServer不理会output.path属性,所以访问正确路径是http://localhost:8080/bundle.js,那么对应的index.html要改一改了:

# index.html

<html>
<head>
  <meta charset="UTF-8">
</head>
<body>
<div id="app"></div>
<!--导入 DevServer 输出的 JavaScript 文件-->
<script src="bundle.js"></script>
</body>
</html>

实时预览

现在好了,随便修改main.js main.css show.js,你会发现浏览器自动刷新了。

Webpack 在启动时可以开启监听模式,开启监听模式后 Webpack 会监听本地文件系统的变化,发生变化时重新构建出新的结果。Webpack 默认是关闭监听模式的,你可以在启动 Webpack 时通过 webpack --watch 来开启监听模式。

通过 DevServer 启动的 Webpack 会开启监听模式,当发生变化时重新执行完构建后通知 DevServer。 DevServer 会让 Webpack 在构建出的 JavaScript 代码里注入一个代理客户端用于控制网页,网页和 DevServer 之间通过 WebSocket 协议通信, 以方便 DevServer 主动向客户端发送命令。 DevServer 在收到来自 Webpack 的文件变化通知时通过注入的客户端控制网页刷新。

如果尝试修改index.html文件并保存,你会发现这并不会触发以上机制,导致这个问题的原因是 Webpack 在启动时会以配置里的 entry 为入口去递归解析出 entry 所依赖的文件,只有1 entry` 本身和依赖的文件才会被 Webpack 添加到监听列表里。 而 index.html 文件是脱离了 JavaScript 模块化系统的,所以 Webpack 不知道它的存在。

模块热替换

除了通过重新刷新整个网页来实现实时预览,DevServer 还有一种被称作模块热替换的刷新技术。 模块热替换能做到在不重新加载整个网页的情况下,通过将被更新过的模块替换老的模块,再重新执行一次来实现实时预览。 模块热替换相对于默认的刷新机制能提供更快的响应和更好的开发体验。 模块热替换默认是关闭的,要开启模块热替换,你只需在启动 DevServer 时带上 –hot 参数,重启 DevServer 后再去更新文件就能体验到模块热替换的神奇了。

source map

想打断点么?只需在启动时带上 –devtool source-map 参数。 加上参数重启 DevServer 后刷新页面,再打开 Chrome 浏览器的开发者工具,就可在 Sources 栏中看到可调试的源代码了。

结束语

webpack,一个打包工具,配置几个参数,加载几个npm命令,入门完毕。注意几个核心概念:

  • entry:入口,可以理解为输入,webpack第一步就找它。
  • module:模块,在webpack里,一切皆模块,一个模块是一个文件,webpack根据模块加载相应依赖。
  • chunk:代码块,一个Chunk 由多个模块组合而成,用于代码合并与分割。
  • Loader:模块转换器(翻译官),用于把模块原内容按照需求转换成新内容。
  • plugin:扩展插件,注入扩展逻辑,做你所想。
  • output:输出结果,webpack处理一系列然后返回结果。

webpack启动—-> 找entry—-> 找entry里的module配置—-> 递归解析所有module依赖—-> 每找到一个,就根据配置的loader去找对应的转换关系—-> 转换后解析当前module—-> 模块以entry为单位分组,一个entry+一个module就是一个chunk—-> webpack把chunk转成文件输出(在恰当时间执行plugin的逻辑)。