52

Webpack 快速上手(上)

 5 years ago
source link: https://mp.weixin.qq.com/s/6vb71N3bOgxsXEl-eqYEVg?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

作者 | 朱士奇

杏仁前端开发工程师,代码洁癖症早期,关注前端技术。

嫌啰嗦想直接看最终的配置请戳这里

webpack-workbench(https://github.com/onlymisaky/webpack-workbench)

由于文章篇幅较长,为了更好的阅读体验,本文分为上、中、下三篇:

  • 上篇介绍了什么是 webpack,为什么需要 webpack,webpack 的文件输入和输出

  • 中篇介绍了 webpack 在输入和输出这段中间所做的事情,也就是 loader 和 plugins

  • 下篇介绍了 webpack 的优化,以及在开发环境和生产环境的不同用法

用两个自问自答来当作序吧:

  • Q:为什么要写这篇文章?

  • A:因为我在将自己的一个项目

    AngularJS-ES6(https://github.com/onlymisaky/AngularJS-ES6) 从 webpack3.x 升级到 4.x 的时候发现,作为一个熟练的 GitHub 搬运工,改起来还是很费力,主要是因为对其没有一个更完整的认知,因此有必要写一篇文章强化认知。

  • Q:既然是写给自己看的,那对于其他人有帮助吗?

  • A:如果你对 webpack 有少许的了解(至少知道webpack是干什么用的),那这篇文章应该还是有帮助的。

Why webpack?

一个工具的诞生,必然有其诞生的原因,也许是为了简化工作,也许是为了解决某些痛点,也可能是今年的kpi压力很大...

今天的主角 webpack 的诞生就是为了解决前端开发长久以来的痛点: 模块化  ,这是也它的前辈   grunt    gulp   所不具备的功能。

回想一下那个前端还被称作切图仔的时代,我们是怎么组织多个 .js   文件的:

<script src="a.js"></script>
<script src="b.js"></script>
<script src="c.js"></script>

且不说这样写有多low,就单从代码维护角度来说, b.js  可能使用了   a.js   中的某个方法;   c.js   同样如此,可能还用到了   b.js   中的某些方法。单看每个文件,是根本不知道这些方法是哪来的,也不清楚这三个文件之间的依赖关系的。

为了解决的这个问题,requirejs(https://github.com/requirejs/requirejs) 诞生了,这是一套   AMD(https://github.com/amdjs/amdjs-api/wiki/AMD)   的模块化实现方案。而此时 node 已经出现有些时日,其遵循的是   CommonJS(http://wiki.commonjs.org/wiki/CommonJS)   ,同样是   JavaScript   模块化,却有两套实现方案,语法也不一样。于是又出现了 C MD( https://github.com/seajs/seajs/issues/242)   和其实现   seajs   ,它是为了让服务端模块化和浏览器端模块化的差异能够最小化。

以上这些都是前辈们对 JavaScript 模块化的探索,虽然不是标准,但却推动了标准的发展,于是在 ES6 中,终于有了标准的、原生的模块化方案了,然鹅...

浏览器厂商:标准是标准,至于什么时候实现,fucked say (日后再议)。虽然现在大部分浏览器内核都实现了原生的模块化,但是我们不能确保用户都已将浏览器更新至最新了。

所以,在所有浏览器都实现模块化标准之前,我们还是不能够愉快的使用 import  export   ,于是 webpack 来了,给乡亲们带了希望,让乡亲们再也不用看浏览器脸色,从此过上了没羞没臊幸福的生活了。

简单介绍

为了有更好更清晰的认识,建议读者跟着文章一起做一遍,可以先创建一个新的文件夹 learn-webpack  ,在该目录中打开命令行,输入   npm init   命令初始化 package.json 文件。

安装

npm i webpack webpack-cli -D

可以全局安装,也可以本地安装,建议本地安装,因为 webpack 不同的版本之间还是有一定的差异,为了避免这个问题,我们选择本地。

在上面安装命令中,除了安装了 webpack 外,还安装了 webpack-cli 。那么这个工具是干什么用的?在 webpack4.x 之后,webpack 把命令行单独提取出来了,也就是说,我们想在命令行中执行 webpack xxx   等命令时,就需要先安装 webpack-cli 。 所以如你的使用的4.x版本的 webpack ,还需要额外安装一下 webpack-cli 。

使用

webpack 的使用还是比较简单的,并且提供了三种使用方法:

  1. 不使用配置文件

webpack <entry> <output>

entry:要打包的文件,可以是一个文件,也可以是一组文件。

output:打包后生成的文件。

例如将 ./src/index.js   打包到   dist/app.js

webpack ./src/index.js dist/app.js
  1. 使用配置文件

不使用配置文件的方式显然不够灵活多变,所以通常都是先编写 webpack 配置文件,然后根据配置文件内容进行打包。在根目录下创建 webpack.config.js   文件,然后在命令行中输入   webpack   ,webpack 会自动读取   webpack.config.js   中的配置内容,然后进行打包,下文将会着重介绍如何编写配置文件。

2. 在node中启动

const webpack = require('webpack');
webpack({
  /* webpack配置内容 */
}, (err, stats) => {
  /* 打包后回调 */
});

npm script

在使用第二种方式的时候,我们也可以将一些配置内容以参数的形式添加在命令后面,比如我们想设置环境为 production   ,可以在 webpack.config.js 中将   mode 设置为   production   ,也可以在命令后面添加   --mode production  

webpack --mode production

如果还需要其他的配置参数,可有继续在后面添加。这样做的好处是可以将一些多变的参数从配置文件中抽离出来,使用起来很灵活。

但是如果参数太多,每次使用的时候又要敲好多命令,可能还会敲错,为了方便管理我们可以将这些命令全部保存在 package.json    scripts   属性中:

{
  "scripts": {
    "dev": "webpack --mode development",
    "build": "webpack --mode production"
  }
}

这样就可以通过 npm run build   命令进行打包了。

核心概念

经常看到有人抱怨 webpack 太难太复杂了,“我们万事俱备,就差一个 webpack 配置工程师了”。确实如此,相比于 gulp   简洁的 api ,webpack 确实复杂了许多。

其实仔细的梳理一下,webpack 最重要也就4个核心概念:

  1. entry 入口

  2. output 出口

  3. loader 模块转换器

  4. plugins 插件

除了这四个核心的概念,剩下的那些都是为了优化代码、让我们能有更好的开发体验而设计的。

mode

开头先讲一个 webpack4 中新增的选项:mode。可能是受   parcel(https://parceljs.org/)   的刺激,webpack4 终于也可以零配置打包了,主要原因是 webpack 终于明白了一个道理:约定大于配置。

model 的值有三种: productiondevelopmentnone  ,分别表示不同模式。

在 production 模式下,会默认启用下面这些插件:

  • process.env.NODE_ENV 的值设为 production

  • FlagDependencyUsagePlugin:删除无用代码

  • FlagIncludedChunksPlugin:删除无用代码

  • ModuleConcatenationPlugin:作用域提升

  • NoEmitOnErrorsPlugin:编译出现错误,跳过输出阶段

  • OccurrenceOrderPlugin

  • SideEffectsFlagPlugin

  • UglifyJsPlugin:js代码压缩

在 development 模式下,会默认启用下面这些插件:

  • process.env.NODE_ENV 的值设为 development

  • devtool 设置为 evel

  • NamedChunksPlugin

  • NamedModulesPlugin

entry

既然是模块化开发,就需要有一个入口文件,相关的模块就可以根据这个入口文件形成一个树形的依赖关系。

当然 webpack 还没有智能到可以自动识别出你的模块依赖关系,所以需要咱们来告诉它,如果你不告诉它,则会默认把 src/index.js (webpack4.x+) 当做入口文件。

入口文件可以是一个文件(string):

// webpack.config.js
module.exports = {
  entry: 'src/main.js'
}

也可以是多个文件(array):

// webpack.config.js
module.exports = {
  entry: ['src/login.js', 'src/logout.js']
}

甚至也可以是一个对象(object):

// webpack.config.js
module.exports = {
  entry: {
    login: 'src/login.js',
    logout: 'src/logout.js',
  }
}

这三种写法的区别是:

  1. 传入一个文件(string)的时候,会把所有具有依赖关系的模块打包生成一个文件;

  2. 传入多个文件(array)的时候,还是会打包生成一个文件,webpack 会把这些文件合并在一起,但是执行的时候会按照数组内文件的顺序依次执行;

  3. 传入对象的时候,则会根据对象key的个数,打包出对应数量的文件;

很显然,传入对象的方式更复杂,但也更利于扩展,同时也适合用来打包多页应用。

output

有进必有出,webpack 也需要我们指定打包后的文件存放位置,也叫做出口文件,和entry 一样,output 也有默认值 dist/main.js (webpack4.x+) 。

下面是 output 常见的配置项:

// webpack.config.js
module.exports = {
  output: {
    path: __dirname + '/dist',
    filename: '[name].bundle.js',
    publicPath: '/assets/'
  }
}
  • path

指定打包后的文件存放位置,注意这是一个 绝对路径   !上面的例子中用了 node 内置的常量 __dirname ,该常量表示当前执行文件所在的目录,所以我们打包出的文件就存放在和 webpack 配置文件同级的   dist   目录下面。

  • filename

打包后的文件名称,该选项有5个可配置项:

配置项 作用 [name] 模块名称,对应 entry 中的 key 值,如果 entry 传入的是 string 或 array 默认为 main [id] 模块id,由 webpack 生成 [hash] 模块的 hash 值,当有文件修改时,这个值就会重新计算并改变 [chunkhash] 这也是一个 hash 值,webpack中每打包生成一个文件,就叫一个chunk ,它是 chunk 本身的 hash ,**通常用它来做文件缓存**


补充一个小知识,如果 entry 中传入的是对象,且对象的 key 值像这种形式 "a/b"  ,并且在   output.filename   中设置了   [name]   那么打包出的文件会存放在   a   文件夹下的   b.js   中( a/b.js )。

  • publicPath

关于这个配置,笔者曾经纠结了好久,知道它的作用,却总是无法理解,在网上看了很多关于 publicPath 的介绍,包括 webpack 的官网,但一直没有豁然开朗的感觉,直到后来在自己的项目中遇到了一些问题,才算是明白了为什么会有这个选项。

如果不想看下面这些内容,可以直接查看 总结   ,建议第一次阅读的时候跳过下面这一小段,等到了   devServer.publicPath   再回过来看一遍。

这里我们可以反向的分析一下。首先,在设置了 path 和 filename 这两个属性之后,便可以确定打包出的文件在本机存放的具体路径了。然后需要明确一点,打包出的代码需要上传到 Web 服务器上,这些文件中可能有 .css .js .png 等等,它们最终都要以 .html 为载体,假设这个文件是 index.html 就像这样:

注意:index.html 是通过 html-webpack-plugin   插件生成的,下文会介绍到。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>webpack</title>

  <link href="index.css" rel="stylesheet">

</head>

<body>
  <img src="hello.png" />

  <script type="text/javascript" src="index.bundle.js"></script>
</body>

</html>

从这段简单的 html 中我们可以得到一个信息,那就是 index.csshello.png  index.bundle.js   这个三个文件都放在相对于 index.html 的 同一级别下面,就像这样:

dist
├── index.html
├── hello.png
├── index.css
└── index.bundle.js

如果我们的 webpack 设置是下面这样的:

// webpack.config.js
module.exports = {
  entry: {
    app: './src/main.js',
  },
  output: {
    path: __dirname + '/dist',
    filename: '[name].bundle.js'
  }
}

那么打包出来的文件结构应该是和上面的一模一样的,我们按照这个结构上传到 Web 服务器,不需要修改什么就可以直接访问了。

一般公司都有专门的 cdn 服务器,那么你可以把 index.css  hello.pngindex.bundle.js   这些资源传到 cdn 服务器上,假设你 cdn 地址是 https://mycdn.com   那么你可以通过   https://mycdn.com/index.css   的方式来访问相应的资源。这时候为了使我们的网站不报错,我们就需要将 index.html 中的资源引用方式改为 :

<link href="https://mycdn.com/index.css" rel="stylesheet">

<img src="https://mycdn.com/hello.png" />

<script type="text/javascript" src="https://mycdn.com/index.bundle.js"></script>

很明显,这种打包完成后还需要手动修改的方式很智障,而如果我们不想做这样修改的话,只需要将 output.publicPath 设置为 https://mycdn.com/ ,便会在打包出来的 index.html 文件内自动加上 output.publicPath 设置的值。

还有一种情况,就是笔者所遇到的情况了。假设你的项目还是传统的开发方式,并没有采用前后端分离,用的还是后端模板的方式。而你作为一个前端开发,你想要模块化开发所以引入了 webpack 来打包,后端的哥们跟你说,你把你打包出的文件放在咱们项目的 static 文件夹下面就行了。一开始都没什么问题,但是在做某一个功能的时候,你发现打包出来的文件体积有点大,而且有些代码可以通过按需加载的方式拆分一下,这时候你想到了用 import()   来动态加载,于是除了打包出了   index.js   ,还有一些需要动态加载的 .js 文件,你把它们都放进了 static 下面。但是在调试的时候却发现,那些需要按需加载的资源无法加载了,全都是 404 ,咦?怎么回事小老弟!打开控制台看一下,所有 404 的资源地址都是   https://test.com/assets/xxx.js   。干!说好的   static   怎么变成了   /assets/   了?后端的哥们跟你说,这是后端框架的原因,虽然你是放在 static 下面,但是请求的时候请求的是   相对于当前页面的 /assets/   这个路径   ,总之   后端没法改,需要前端想办法解决 。这个时候,我们只要把 output.publicPath 设置为   /assets/   就可以解决这个问题了。

总结:

这个选项默认是 ''   ,一般情况是不需要修改的。但是在有些情况下,打包出的资源部署上线后,可能会出现   404   访问不到的情况。这个时候就需要配置一下这个选项来解决这个问题了。

如果你将打包后的资源上传到 cdn 上面,那么需要将它设置为可以通过 cdn 方式访问的地址,比如   publicPath: 'https://mycdn.com/assets/'  

如果你的项目在服务器上面目录结构和你打包出的文件结构不一样,比如你打包出来的 .html 和 .js 是平级的,但是在服务器上却把 .js 文件都放在 /assets 下面,那你需要设置为 publicPath: /assets/'  

所以这个值 并不会影响你打包出的文件路径,它只是用来设置在线上运行的时候,所请求的资源相对于 服务 /html页面 的路径

简单的说,在线上运行的时候,所请求的资源具体路径是 https://你的域名/publicPath/资源   或者   https://你设置的cdn地址/资源  

output 的常用配置项就这三个,如果你想用 webpack 把你的代码打包成类库,你还需要配置一下 output.library    output.libraryTarget   等,不过笔者建议直接使用   rollup(https://rollupjs.org/)   打包类库。

所以如果有下面这样一份 webpack 配置文件:

// webpack.config.js
module.exports = {
  entry: {
    app: './src/main.js',
  },
  output: {
    path: __dirname + '/dist',
    filename: '[name].bundle.js'
  }
}

会打包出如下这些文件:

project
├── src // 源代码文件夹
│   ├── main.js
│   ├── login.js
│   └── logout.js
├── dist // 打包后生成的文件夹
│   └── app.bundle.js
└── webpack.config.js  // webpack 位置文件

总结:本篇介绍了前端模块化的发展历史和如何使用 webpack 打包实现模块化,那么在打包过程中 webpack 是如何多不同的文件进行解析、编译、处理的呢?在下一篇中将会详细的介绍。

全文完

以下文章您可能也会感兴趣:

我们正在招聘 Java 工程师,欢迎有兴趣的同学投递简历到 [email protected]

IRRbQr6.jpg!web

杏仁技术站

长按左侧二维码关注我们,这里有一群热血青年期待着与您相会。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK