显微镜下的webpack4入门

前端的构建打包工具很多,比如grunt,gulp。相信这两者大家应该是耳熟能详的,上手相对简单,而且所需手敲的代码都是比较简单的。然后webpack的出现,让这两者打包工具都有点失宠了。webpack比起前两者打包工具,对于前端程序员JS编程能力的要求还是挺高的。不过需要兼容ie8及以下的小伙伴们,就不要考虑webpack了,他很傲娇地不兼容!

webpack 前期准备

webpack,这是一个组合词“web”+“pack”,web就是网站的意思,“pack”有打包的意思,webpack组合在一起就是网站打包的意思,这个名字相当暴力简单明了啊。webpack这款工具虽然很难学,但是自由度很大,玩转之后有种随心所欲的感觉。

在学习webpack之前,有几个基础的概念:

  • JavaScript,如果这个编程能力不过关,比如不清楚ES6的语法,那么webpack学起来有些费力,还是要先去学习基础知识。
  • nodejs,关于nodejs的日常用法,还是需要了解的,不然webpack改如何启动,都无从下手。
  • CommonJS,这个规范是需要学习下的,webpack的配置文件就是按照这个规则。
  • 如果以上几个技能都具备,那么恭喜我们可以开始webpack的学(求)习(虐)之旅了。

webpack 打包原理

在使用webpack之前,我们需要了解webpack的工作原理。webpack打包出来的JS不仅仅是压缩混淆我们的源文件,而且还对它做了其他的处理。

下面是webpack打包出来的JS文件和源文件:

  • "./src/index.js"源文件
let str="index"
console.log(str)
  • webpack打包后
(function(modules) { // webpackBootstrap
    /*此处省略N+1行*/
    return __webpack_require__(__webpack_require__.s = "./src/index.js");
})
({
    "./src/index.js":(function(module, exports) {
        eval("let str=\"index\"\r\nconsole.log(str)\n\n//# sourceURL=webpack:///./src/index.js?");
    })
});

是不是感觉本来小巧的JS,一下子变得臃肿了??似乎用webpack没有意义啊!不仅不能忙我压缩文件,还把源文件变胖了。

不要急,我们再看一个例子:

  • "./src/index.js"源文件
require("./page1.js")
let str="index"
console.log(str)
  • "./src/page1.js"源文件
let str="page1"
console.log(str)
  • webpack打包后
(function(modules) { // webpackBootstrap
    /*此处省略N+1行*/
    return __webpack_require__(__webpack_require__.s = "./src/index.js");
})
({
    "./src/index.js": (function(module, exports, __webpack_require__) {
        eval("__webpack_require__(/*! ./page1.js */ \"./src/page1.js\")\r\nlet str=\"index\"\r\nconsole.log(str)\n\n//# sourceURL=webpack:///./src/index.js?");
    }),
    "./src/page1.js":(function(module, exports) {
        eval("let str=\"page1\"\r\nconsole.log(str)\n\n//# sourceURL=webpack:///./src/page1.js?");
    })
});

当有模块导入的时候,这个胖JS就展现了他真正的实力。通过__webpack_require__来实现JS之间导入的功能。相当于我们不再需要用requirejs,seajs此类包管理器管理我们的前端模块了。webpack帮助我们完成了此类工作。是不是突然觉得这个胖JS不胖了。

webpack的打包原理,就是将各个模块变成字符串,存入健值或者数组之中,然后每个模块之间的关系,通过__webpack_require__这个方法来实现。最后通过eval这个函数将字符串变成可执行代码。

如果大家对__webpack_require__的实现原理感兴趣,可以自己打包一个文件,不要压缩混淆,然后研究研究。

对webpack的期许

webpack这个工具,不可能只有打包压缩这个功能吧。既然是前端工具,那么必然要具备以下功能:

  • 代码处理,如打包,编译等
  • 自动生成HTML文件,比如模板生成页面
  • 本地服务器,这个是必备功能,不然无法调试页面
  • 自动编译代码,刷新浏览器,这个大家喜欢称之为hot replacement(热替换,热更新),也就是(修改过的)部分更新
  • 那我们逐步来了解下webpack这些功能该如何实现。

webpack从0开始

如果你之前并未使用过webpack,那么就需要安装一下webpack,顺便学习下如何启动webpack。

STEP 1 INSTALL

webpack从4开始,webpack分成了两个包一个webpack一个webpack-cli,所以安装的时候要安装两个包,以及这个包我们是工具,非网站所依赖的包,所以记得放在开发依赖包之中。

npm install webpack webpack-cli -save-dev

也许我们想可以直接安装webpack,不要webpack-cli。但是现实很残酷,如果没有安装CLI,系统就会告诉你,cli是必不可少的,不然webpack就罢工了。

One CLI for webpack must be installed.

STEP2 RUN

安装好了之后,我们应该怎么运行呢?这里有两个途径:

  • npm v8.5以上有一个操作叫做npx,这个是干嘛的呢,是帮忙我们直接执行.bin,目录下的文件。node_modules\.bin\webpack.cmd在这个路径下有webpack的执行命令,我们可以打开看看。当我们npx webpack的时候,就是运行了这个文件。
  • 通过配置package.json来运行文件,有个字段叫做scripts,我们加一个start,然后后面跟上命令。到时候我们呼唤npm start就要可以运行webpack了。
"scripts": {
    "start": "webpack --config webpack.config.js"
 }

webpack4开始支持零配置,也就是说我不用写webpack.config.js也可以运行。那我们就运行试试,结果出现了一个警告:

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/

这个警告就是告诉我们,webpack4中的mode参数默认是production,所以如果是development的情况就一定要配置了。感觉是零配置似乎是非常牛逼的一个操作,但是实际上还是需要手动配置的,因为这个零配置只是帮我们做掉了一些简单的事,比如线上就压缩JS,开发版就不压缩JS,还有一些默认的路径之类的。实际上开发的时候,默认的路径肯定是不够用的。我们还是老老实实写配置吧。

我们配置一下,并且运行一下,在开发环境下打包,生成了一个/dist/main.js文件。奇怪我的html文件怎么没有打包过来?对,HTML文件需要我们自己在dist之中创建的,也就是/dist/index.html。并且路径要写好即将生成的JS链接。比如/dist/main.js在html中引入,我就需要写成

module.exports = {
    mode:"development",
};

这个配置文件,大家都没有觉得写法很熟悉?对!就是CommonJs规范!下一节会详细解释webpack.config.js该如何配置。

webpack的心脏——webpack.config.js

显微镜下的webpack4入门_第1张图片

webpack的一切操作都配置在webpack.config.js之中,可以说配好webpack.config.js,我们就可以坐等新鲜出炉的网站了。

从官方文档来看,webpack一共有5个主要地配置参数:

  • Entry:切入点,也就是JS程序入口,按照这个入口开始创建模块依赖,默认./src/index.js
  • Output:输出口,打包程序的安放位置,默认./dist/main.js
  • Loaders:加载器,将除了JS和JSON以外的文件解析加载,比如txt,css等等。
  • Plugins:插件,可以做一些更加牛逼的效果,一般要new一个插件对象。
  • Mode(新增):productiondevelopment,这个是webpack4新增的一个属性,用于区分开发版与线上版,也是很贴心的设置了。

Entry&Output,以及chunk的概念

在学些webpack的配置之前,我们最先接触的就是输入Entry和输出Output的配置。这里需要引入一个chunk的概念,我们在配置Entry的时候,有时候会设置好多个入口,这每一个入口都是一个chunk,也就是说chunk是根据Entry的配置而来的。大家一定要区分清楚chunk和module的概念啊。module就是编程的时候,我们所写的一块一块的功能块,然后模块之间会有依赖。然后chunk只是将当前模块和他的依赖模块,一起打包起来的代码块。

配置Entry,切入点JS入口也不是件容易的事。

Entry配置

  • 单一入口,单个文件。整个程序只有一个JS,这个配置就很简单了,我么也可以不配置,因为默认./src/index.js。单个文件之间传入字符串即可。
entry: '需要打包的JS的相对或者绝对地址'
  • 单一入口,多个文件。有时候我们有好多独立的JS文件,但是我只想导出一个JS,这个时候就需要传入数组了。
entry: ["待打包JS-1","待打包JS-2"]
  • 多个入口,单个文件。这个时候我们就要配置健值了,都是默认值,怎么识别谁是谁。一般来说一个HTML只需要一个chunk,也就是一个入口点。所以这个一般用于多张页面的配置。
entry: {
    JS1: "待打包JS-1",
    JS2: "待打包JS-2"
}
  • 多个入口,多个文件。前面提到一个HTML只需要一个入口点,所以这里我们可以借鉴数组来完成此操作。
entry: {
    JS1: ["待打包JS1-1","待打包JS1-2"],
    JS2: ["待打包JS2-1","待打包JS2-2"]
}

Output配置

输出口,安放打包好的JS,不配置就打包到默认文件,默认./dist/main.js

如果不需要分入口点,整个网站用一个JS。那么配置一个文件名就可以了。

output: {
    filename: 'bundle.js',
}

需要指定文件夹的操作,就再加一个path字段即可。

output: {
    filename: 'bundle.js',
    path: __dirname + '/dist'
}

然而现实中,我们不可能只有一个JS,所以这个时候我们就需要配置多个输出口,不过这个不像entry可以配置健值。但是有一个很简便的办法filename: '[name].js',文件名我们用[name],这样打包出来的Js文件就会按照Entry配置的chunk名,来命名了。

当然我们经常回碰到CDN的问题,一个JS会被缓住,这时候我们可以用[hash]这个参数,来帮我们filename: '[name].[hash].js'这样每次生成的JS名就不一样了。

LOADER,模块的概念

在webpack中,任何文件都可以变成一个模块,然后被打包到JS之中。但是JS只认识JS,像CSS,或者typescript这类的非标准JS,该如何处理?这个时候Loader就出现了,他帮助webpack将CSS此类文件变成JS可识别的内容然后打包。所有的loader都需要额外下载安装,这里以最常用的CSS为例子,看我们如何将CSS打包到JS之中。

  • 安装css-loader这个加载器
npm install --save-dev css-loader

关于css-loader的用法,大家可以参考下官网。

  • 在webpack中配置。大家不要把loader的配置名写成了loader,他的在webpack中的配置名是module.rule
module: {
    rules: [
        {
          test: /\.css$/,
          use: [
            { loader: 'style-loader'},
            { loader: 'css-loader',options: {modules: true}}
          ]
        }
      ]
}
  • 添加style-loader

也就是说loader所有的配置都在rules之下。这里我还配置了style-loader,那么我们既然又了css-loader为什么还要style-loader呢?感觉很累赘啊。那么接下来就要说说这两个loader的不同了。

打开styleloader的官网,我们可以发现:

Adds CSS to the DOM by injecting a