WebPack

1. 核心概念

本质上,webpack 是一个现代 JavaScript 应用程序的静态
模块打包器(module bundler)。当 webpack 处理应用程序
时它会递归地构建一个依赖关系图(dependency graph),其
中包含应用程序需要的每个模块,然后将所有这些模块打包
成一个或多个 bundle。

 

1.1. 入口(entry)

入口起点(entry point)指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后,webpack 会找出有setting.json哪些模块和库是入口起点(直接和间接)依赖的。

  1. webpack 配置中配置 entry 属性
  2. 默认值为 ./src
  3. webpack.config.js
1
2
3
module.exports = {
entry: './path/to/my/entry/file.js'
};

1.2. 出口(output)


output 属性告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件。基本上,整个应用程序结构,都会被编译到你指定的输出路径的文件夹中。 1.webpack 配置中配置 entry 属性 2.默认值为./dist 3.webpack.config.js
1
2
3
4
5
6
7
8
9
const path = require('path');

module.exports = {
entry: './path/to/my/entry/file.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-first-webpack.bundle.js'
}
};

1.3. loader

loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。

本质上,webpack loader 将所有类型的文件,转换为应用程序的依赖图(和最终的 bundle)可以直接引用的模块。

  1. test 属性,用于标识出应该被对应的loader进行转换的某个或某些文件。

  2. use 属性,表示进行转换时,应该使用哪个loader。

  3. webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const path = require('path');

const config = {
output: {
filename: 'my-first-webpack.bundle.js'
},
module: {
rules: [
{ test: /\.txt$/, use: 'raw-loader' }
]
}
};

module.exports = config;

“嘿,webpack 编译器,当你碰到「在 require()/import 语句中被解析为 ‘.txt’ 的路径」时,在你对它打包之前,先使用 raw-loader 转换一下。”


1.4. 插件(plugins)

想要使用一个插件,你只需要 require() 它,然后把它添加到 plugins 数组中。多数插件可以通过选项(option)自定义。你也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用 new 操作符来创建它的一个实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 通过 npm 安装
const webpack = require('webpack'); // 用于访问内置插件

const config = {
module: {
rules: [
{ test: /\.txt$/, use: 'raw-loader' }
]
},
plugins: [
new HtmlWebpackPlugin({template: './src/index.html'})
]
};

module.exports = config;

1.5. 模式

通过选择 development 或 production 之中的一个,来设置 mode 参数,你可以启用相应模式下的 webpack 内置的优化

1
2
3
module.exports = {
mode: 'production'
};

2. 入口起点(entry points)


2.1. 单个入口

1
2
3
4
5
const config = {
entry: './path/to/my/entry/file.js'
};

module.exports = config;

2.2. 多个入口

1
2
3
4
5
6
const config = {
entry: {
app: './src/app.js',
vendors: './src/vendors.js'
}
};

“可扩展的 webpack 配置”是指,可重用并且可以与其他配置组合使用。这是一种流行的技术,用于将关注点(concern)从环境(environment)、构建目标(build target)、运行时(runtime)中分离。然后使用专门的工具(如 webpack-merge)将它们合并。


2.3. 常见场景

2.3.0.1. 分离 应用程序(app) 和 第三方库(vendor) 入口

1
2
3
4
5
6
const config = {
entry: {
app: './src/app.js',
vendors: './src/vendors.js'
}
};

这是什么?从表面上看,这告诉我们 webpack 从 app.js 和 vendors.js 开始创建依赖图(dependency graph)。这些依赖图是彼此完全分离、互相独立的(每个 bundle 中都有一个 webpack 引导(bootstrap))。这种方式比较常见于,只有一个入口起点(不包括 vendor)的单页应用程序(single page application)中。

为什么?此设置允许你使用 CommonsChunkPlugin 从「应用程序 bundle」中提取 vendor 引用(vendor reference) 到 vendor bundle,并把引用 vendor 的部分替换为 webpack_require() 调用。如果应用程序 bundle 中没有 vendor 代码,那么你可以在 webpack 中实现被称为长效缓存的通用模式。

2.4. 多页面应用程序

1
2
3
4
5
6
7
const config = {
entry: {
pageOne: './src/pageOne/index.js',
pageTwo: './src/pageTwo/index.js',
pageThree: './src/pageThree/index.js'
}
};

这是什么?我们告诉 webpack 需要 3 个独立分离的依赖图(如上面的示例)。

为什么?在多页应用中,(译注:每当页面跳转时)服务器将为你获取一个新的 HTML 文档。页面重新加载新文档,并且资源被重新下载。然而,这给了我们特殊的机会去做很多事:

使用 CommonsChunkPlugin 为每个页面间的应用程序共享代码创建 bundle。由于入口起点增多,多页应用能够复用入口起点之间的大量代码/模块,从而可以极大地从这些技术中受益。

3. 输出

配置 output 选项可以控制 webpack 如何向硬盘写入编译文件。注意,即使可以存在多个入口起点,但只指定一个输出配置。

3.1. 用法(usage)

在 webpack 中配置 output 属性的最低要求是,将它的值设置为一个对象,包括以下两点:

  • filename 用于输出文件的文件名。
  • 目标输出目录 path 的绝对路径。
1
2
3
4
5
6
7
8
const config = {
output: {
filename: 'bundle.js',
path: '/home/proj/public/assets'
}
};

module.exports = config;

3.2. 多个入口起点

1
2
3
4
5
6
7
8
9
10
11
12
	const config = {
entry: {
app: './src/app.js',
search: './src/search.js'
},
output: {
filename: '[name].js',
path: __dirname + '/dist'
}
};

// 写入到硬盘:./dist/app.js, ./dist/search.js

3.3. 高级进阶

3.3.0.2. 以下是使用 CDN 和资源 hash 的复杂示例:

1
2
3
4
output: {
path: "/home/proj/cdn/assets/[hash]",
publicPath: "http://cdn.example.com/assets/[hash]/"
}

在编译时不知道最终输出文件的 publicPath 的情况下,publicPath 可以留空,并且在入口起点文件运行时动态设置。如果你在编译时不知道 publicPath,你可以先忽略它,并且在入口起点设置 webpack_public_path

1
2
3
__webpack_public_path__ = myRuntimePublicPath

// 剩余的应用程序入口
3.3.0.2.0.1. publicPath(扩展)

webpack 提供一个非常有用的配置,该配置能帮助你为项目中的所有资源指定一个基础路径,它被称为公共路径(publicPath)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
output.publicPath = '/dist/'

// image
options: {
name: 'img/[name].[ext]?[hash]'
}

// 最终图片的访问路径为
output.publicPath + 'img/[name].[ext]?[hash]' = '/dist/img/[name].[ext]?[hash]'

// js output.filename
output: {
filename: '[name].js'
}
// 最终js的访问路径为
output.publicPath + '[name].js' = '/dist/[name].js'

// extract-text-webpack-plugin css
new ExtractTextPlugin({
filename: 'style.[chunkhash].css'
})
// 最终css的访问路径为
output.publicPath + 'style.[chunkhash].css' = '/dist/style.[chunkhash].css'

这个最终静态资源访问路径在使用html-webpack-plugin打包后得到的html中可以看到。所以publicPath设置成相对路径后,相对路径是相对于build之后的index.html的,例如,如果设置publicPath: ‘./dist/‘,则打包后js的引用路径为./dist/main.js,但是这里有一个问题,相对路径在访问本地时可以,但是如果将静态资源托管到CDN上则访问路径显然不能使用相对路径,但是如果将publicPath设置成/,则打包后访问路径为localhost:8080/dist/main.js,本地无法访问.
所以这里需要在上线时候手动更改publicPath,感觉不是很方便,但是不知道该如何解决…

一般情况下publicPath应该以’/‘结尾,而其他loader或插件的配置不要以’/‘开头

4. 模式

提供 mode 配置选项,告知 webpack 使用相应模式的内置优化。

  • development
    1
    2
    3
    4
    5
    6
    7
    8
    // webpack.development.config.js
    module.exports = {
    + mode: 'development'
    - plugins: [
    - new webpack.NamedModulesPlugin(),
    - new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development") }),
    - ]
    }
  • production
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // webpack.production.config.js
    module.exports = {
    + mode: 'production',
    - plugins: [
    - new UglifyJsPlugin(/* ... */),
    - new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("production") }),
    - new webpack.optimize.ModuleConcatenationPlugin(),
    - new webpack.NoEmitOnErrorsPlugin()
    - ]
    }

5. loader

loader 用于对模块的源代码进行转换。loader 可以使你在 import 或”加载”模块时预处理文件。因此,loader 类似于其他构建工具中“任务(task)”,并提供了处理前端构建步骤的强大方法。loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript,或将内联图像转换为 data URL。loader 甚至允许你直接在 JavaScript 模块中 import CSS文件!

5.1. 示例

例如,你可以使用 loader 告诉 webpack 加载 CSS 文件,或者将 TypeScript 转为 JavaScript。然后指示 webpack 对每个 .css 使用 css-loader,以及对所有 .ts 文件使用 ts-loader:

1
2
3
4
5
6
7
8
module.exports = {
module: {
rules: [
{ test: /\.css$/, use: 'css-loader' },
{ test: /\.ts$/, use: 'ts-loader' }
]
}
};

5.2. 使用loader

在你的应用程序中,有三种使用 loader 的方式:

配置(推荐):在 webpack.config.js 文件中指定 loader。
内联:在每个 import 语句中显式指定 loader。
CLI:在 shell 命令中指定它们。

5.2.0.3. 配置(configuration)

module.rules 允许你在 webpack 配置中指定多个 loader。 这是展示 loader 的一种简明方式,并且有助于使代码变得简洁。同时让你对各个 loader 有个全局概览:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module: {
rules: [
{
test: /\.css$/,
use: [
{ loader: 'style-loader' },
{
loader: 'css-loader',
options: {
modules: true
}
}
]
}
]
}

5.2.0.4. 内联

可以在 import 语句或任何等效于 “import” 的方式中指定 loader。使用 ! 将资源中的 loader 分开。分开的每个部分都相对于当前目录解析。

1
import Styles from 'style-loader!css-loader?modules!./styles.css';

通过前置所有规则及使用 !,可以对应覆盖到配置中的任意 loader。

选项可以传递查询参数,例如 ?key=value&foo=bar,或者一个 JSON 对象,例如 ?{“key”:”value”,”foo”:”bar”}。

尽可能使用 module.rules,因为这样可以减少源码中的代码量,并且可以在出错时,更快地调试和定位 loader 中的问题。

5.2.0.5. CLI(命令行操作)


5.3. loader特性

  • loader 支持链式传递。能够对资源使用流水线(pipeline)。一组- 链式的 loader 将按照相反的顺序执行。loader 链中的第一个 - - loader 返回值给下一个 loader。在最后一个 loader,返回 webpack 所预期的 JavaScript。
  • loader 可以是同步的,也可以是异步的。
  • loader 运行在 Node.js 中,并且能够执行任何可能的操作。
  • loader 接收查询参数。用于对 loader 传递配置。
  • loader 也能够使用 options 对象进行配置。
  • 除了使用 package.json 常见的 main 属性,还可以将普通的 npm 模块导出为 loader,做法是在 package.json 里定义一个 loader 字段。
  • 插件(plugin)可以为 loader 带来更多特性。
  • loader 能够产生额外的任意文件。

5.4. 解析 loader

loader 遵循标准的模块解析。多数情况下,loader 将从模块路径(通常将模块路径认为是 npm install, node_modules)解析。

loader 模块需要导出为一个函数,并且使用 Node.js 兼容的 JavaScript 编写。通常使用 npm 进行管理,但是也可以将自定义 loader 作为应用程序中的文件。按照约定,loader 通常被命名为 xxx-loader(例如 json-loader)。


6. 插件

插件目的在于解决 loader 无法实现的其他事。

  1. apply 属性会被 webpack compiler 调用,并且 compiler 对象可在整个编译生命周期访问。

7. 配置分离(-merge)

我们在根目录下创建config文件夹,并创建四个配置文件:

  • webpack.comm.js 公共环境的配置文件
  • webpack.development.js 开发环境下的配置文件
  • webpack.production.js 生产环境下的配置文件
  • webpack.parts.js 各个配置零件的配置文件

webpack-merge做了两件事:它允许连接数组并合并对象,而不是覆盖组合。

1
2
3
4
5
6
7
const merge = require("webpack-merge");
merge(
{a : [1],b:5,c:20},
{a : [2],b:10, d: 421}
)
//合并后的结果
{a : [1,2] ,b :10 , c : 20, d : 421}

8. 外部扩展(extrenals)

防止将某些 import 的包(package)打包到 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖(external dependencies)。

例如,从 CDN 引入 jQuery,而不是把它打包.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//index.html
<script
src="https://code.jquery.com/jquery-3.1.0.js"
integrity="sha256-slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk="
crossorigin="anonymous">
</script>

//webpack.config.js
externals: {
jquery: 'jQuery'
}

//这样就剥离了那些不需要改动的依赖模块,换句话,下面展示的代码还可以正常运行:
import $ from 'jquery';

$('.my-element').animate(...);

tableTest

姓名(默认左对齐)) 技能(两遍都有是中间) 排行(右侧冒号右对齐)
刘备 大哥
关羽 二哥
张飞 三弟
一键三连!