Webpack 快速上手(上)

栏目: 编程语言 · 发布时间: 5年前

内容简介:作者 | 朱士奇杏仁前端开发工程师,代码洁癖症早期,关注前端技术。嫌啰嗦想直接看最终的配置请戳这里

作者 | 朱士奇

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

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

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 工程师,欢迎有兴趣的同学投递简历到 rd-hr@xingren.com 。

Webpack 快速上手(上)

杏仁技术站

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


以上所述就是小编给大家介绍的《Webpack 快速上手(上)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

法律程序的意义:对中国法制建设的另一种思考

法律程序的意义:对中国法制建设的另一种思考

季卫东 / 中国法制出版社 / 2005-1 / 10.0

《法律程序的意义:对中国法制建设的另一种思考》内容为现代程序的概念与特征、现代程序的结构与功能、程序与现代社会、中国法律程序的缺陷、程序建设的程序等。一起来看看 《法律程序的意义:对中国法制建设的另一种思考》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

html转js在线工具
html转js在线工具

html转js在线工具