Menu

  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay
  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay

Webpack学习笔记

7
Dec
2016

Webpack学习笔记

By Alex
/ in JavaScript
0 Comments
简介

Webpack是用来在应用程序中构建JavaScript模块的工具,它能够快速的构造出应用程序的依赖图,并且按照正确的顺序将它们捆绑(bundling)在一起。Webpack还支持根据配置进行代码最优化。你可以通过命令行工具、API两种方式来使用Webpack。

模块捆绑器

Webpack通常被用来和Make、Grunt、Gulp、Browserify、Brunch之类的工具进行比较,实际上它们并不是同类型的工具。Make、Grunt、Gulp可以归类为任务运行器(task runner),而Webpack、Browserify、Brunch则可以归类为模块捆绑器(module bundler)。

任务运行器的作用是让一系列任务——代码检查、构建——变得容易。模块捆绑器则具有更特殊的目标:将一系列资产——例如JavaScript、CSS——交由捆绑器,它会将其输出为适合浏览器、最终用户使用的格式。

任务运行器可以和模块捆绑器协同工作, grunt-webpack、gulp-webpack就是集成两者的例子。Webpack用户也经常使用npm的Stage脚本作为任务运行器。

尽管Webpack的核心关注于捆绑,但是它的一些扩展类似于任务运行器。

Webpack的优势
  1. 支持开发者模式,可以热替换浏览器中的代码而不需要页面刷新
  2. 支持多种模块化机制:
    1. AMD的define、require
    2. CommonJS的exports、require、require.resolve
    3. 支持ES6模块
  3. 支持调试:SourceUrl、SourceMaps
  4. 支持基于uglify进行代码最小化
  5. 支持基于插件的扩展
  6. 使用异步I/O和多级缓存,性能优异,特别是增量编译时
  7. 支持按块(Chunk)加载,将整个依赖图拆分为多个Chunk加载,以提高响应速度
  8. 万物皆模块,任何静态资源都可以作为模块加载
新特性
Webpack4

配置文件不再是必须的。

Webpack2

内置ES6支持

Webpack2内置了对ES6模块的支持。ES6加载器通过 System.import 在运行时动态加载ES6模块。并且支持ES6、AMD、CommonJS在同一文件中混合使用

Webpack2支持以System.import为拆分点(splitpoint),把每个请求的ES6模块放到独立的块(Chunk)中

如果联合使用Babel,应该改用es2015-webpack这个预设,这样才能让Webpack来处理ES6模块。es2015预设会把ES6模块转换为CommonJS格式

名词术语
术语 说明
Entry

为了创建Bundle(即捆绑了所有依赖的、由HTML直接引用的JS文件),Webpack需要分析依赖图,所谓Entry就是分析此依赖图的入口点

一般入口点是启动应用时第一个加载的JS文件。默认值是 ./src

Output 一旦Webpack把所有资产捆绑到一起,它需要将其输出到工程的某个目录中供客户端使用。默认值是./dist
Modules

在模块化编程的术语中,所谓模块是独立的具有特定功能的块

在Webpack中,模块可以是:CoffeeScript、TypeScript、ESNext (Babel)、Sass、Less、Stylus等。模块之间可以具有依赖关系,声明依赖的方式包括:

  1. ES6的import语句
  2. CommonJS的require()调用
  3. AMD的define/require语句
  4. CSS中的@import语句
  5. CSS中的图片URL
  6. HTML中的图片URL
Loaders

Webpack把入口点所有直接/间接依赖的文件(CSS、HTML、SCSS、JPG等)都看作“模块”,然而Webpack本身只能理解JavaScript

所谓Loader(加载器),其用途就是把所有非JavaScript文件都转换为模块,同时加入到依赖图中。Loader本身是一个函数,它接受源文件作为入参,并返回转换后的结果。Loader的主要特性包括:

  1. 支持链式调用多个Loader,每个Loader都负责将资源转换为其它格式,但最终必须转换为JavaScript。因为只有JavaScript才能被Webpack本身理解
  2. Loader可以同步或者异步运行

按照惯例,Loader命名一般均以-loader作为后缀,在配置文件中引用Loader时可以省略后缀

下面是Webpack 2.x配置Loader的示例:

webpack.config.js
JavaScript
1
2
3
4
5
6
7
module: {
    rules: [
        // 对于任意模块系统的导入语句(require/import),只要其导入目标是js或者jsx文件的
        // 在这些文件被加入到bundle之前,调用Babel进行转换
        { test: /\.(js|jsx)$/, use: 'babel-loader' }
    ]
}

在Webpack配置文件中,你需要参考上面的代码,在rules属性中来声明Loader:

  1. test属性:识别哪些文件需要被某个Loader转换
  2. use属性:使用什么Loader来转换test匹配的文件,并将其添加到依赖图(最终添加到bundle中)
Plugins

Loader在文件级别操作,它转换的是单个文件。Plugin(插件)则用于更大范围的操作 —— 负责转换某些类型的模块。Webpack的插件系统非常强大和可扩展。插件可以做的事情包括:

  1. 打包优化和压缩
  2. 重新定义环境中的变量

要使用插件,你需要在webpack.config.js中require()它并在plugins中配置:

webpack.config.js
JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
const HtmlWebpackPlugin = require('html-webpack-plugin');
// 下面这行用于访问内置插件
const webpack = require('webpack');
 
const config = {
    plugins: [
        new webpack.optimize.UglifyJsPlugin(),
        new HtmlWebpackPlugin({template: './src/index.html'})
    ]
};
 
module.exports = config;

由于你可以基于不同意图多次使用同一种插件,因此每次都需要new一个插件实例 

mode 通过选择 development 或 production 之中的一个,来设置 mode 参数,你可以启用相应模式下的 webpack 内置的优化
JavaScript
1
2
3
module.exports = {
  mode: 'production'
};

模式的区别:

  1. development:会将 process.env.NODE_ENV 的值设为 development。启用 NamedChunksPlugin 和 NamedModulesPlugin
  2. production:会将 process.env.NODE_ENV 的值设为 production。启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 UglifyJsPlugin
安装

在Webpack之前,确保合适版本的Node.js已经安装在你的系统中,当前LTS版本是推荐的选择。

要全局的使用webpack命令行,可以执行全局安装:

Shell
1
npm install webpack -g

但是通常并不推荐(仅仅)这样做,因为这使得所有工程使用相同版本的ESLint。最好是针对工程进行本地安装:

Shell
1
npm install webpack --save-dev

通好npm调用webpack时,总是会优先在本地模块目录中寻找Webpack。 

入门

执行下面的命令,创建一个示例工程:

Shell
1
2
3
4
5
mkdir WebpackStudy && cd WebpackStudy
npm init -y
npm install --save-dev webpack
# 该示例使用下面这个库的功能
npm install --save lodash
不使用Webpack

创建一个HTML:

index.html
XHTML
1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Webpack Study</title>
    <script src="https://unpkg.com/lodash@4.16.6" type="text/javascript"></script>
</head>
<body>
<script src="index.js" type="text/javascript"></script>
</body>
</html>

上面HTML使用的脚本如下:

index.js
JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
function component() {
    var element = document.createElement( 'div' );
 
    /* lodash提供了 _ 变量  */
    element.innerHTML = _.map( [ 'Hello', 'webpack' ], function ( item ) {
        return item.toUpperCase();
    } ).join( ' ' );
 
    return element;
}
 
document.body.appendChild( component() );

这个示例中,index.js依赖于lodash,而index.html的script声明顺序,确保了此依赖关系得到满足,因此程序可以正常运行。

程序规模大了以后,很难用这种手工的方式来管理依赖。如果依赖不小心缺失或者script标签声明顺序不对,程序无法运行;如果包含了根本没用的依赖,则为客户端、服务器增加不必要的流量负担,无效依赖中的脚本还可能影响客户端性能。

通过Webpack来bundle

使用Webpack来管理依赖,可以解决上面的问题。你需要为工程引入某种模块化机制,例如ES6模块化系统:

index.js
JavaScript
1
2
// 在文件开头添加
import _ from 'lodash';

同时修改index.html:

XHTML
1
2
3
4
5
<!-- 添加:-->
<script src="dist/bundle.js" type="text/javascript"></script>
<!-- 删除:-->
<script src="https://unpkg.com/lodash@4.16.6" type="text/javascript"></script>
<script src="index.js" type="text/javascript"></script>

改造以后,index.js通过import语句显式的声明对lodash的依赖,使用模块化的lodash还避免了全局名字空间的污染。

Webpack就是应用这里的import语句,来构建一个依赖图,并根据此依赖图,生成最优化的bundle,在bundle中脚本以正确的顺序被执行。如果import一个没有使用的依赖,它不会被包含在bundle中。

bundle的创建不会自动执行,因为分析需要消耗计算资源。我们可以通过命令行触发bundle的构建:

Shell
1
node_modules/.bin/webpack index.js dist/bundle.js

输出文件bundle.js包含了运行页面的全部代码。但是转换后的代码并不能在浏览器中运行,这是因为Webpack 1.x不能原生的支持ES6 import语句,目前正在开发中的Webpack 2原生支持。

要转换为ES5兼容的代码,可以使用babel-loader加载器。

使用配置文件

总是在命令行中指定参时比较麻烦,可以指定配置文件。当前目录下的 webpack.config.js 文件会自动被webpack命令读取。你也可以通过 --config 选项指定任意配置文件。

与上节webpack命令调用参数等价的配置文件为:

webpack.config.js
JavaScript
1
2
3
4
5
6
7
module.exports = {
    entry: './index.js',
    output: {
        filename: 'bundle.js',
        path: './dist'
    }
}

配置文件的用途不仅仅是指定入口点和输出文件位置,loader rules、plugins、resolve以及很多其它配置项,可以进一步增强bundle是其适用于生产环境。

在npm中使用

要通过npm来调用Webpack,只需要将其webpack命令挂到某个Stage脚本下:

package.json
JavaScript
1
2
3
4
5
{
    "scripts": {
        "build": "webpack"
    }
}

然后执行: npm run build 即可。 要通过npm命令行读取Webpack参数,可以使用 -- 中断npm本身的配置参数的读取:

Shell
1
2
# -- 后面的将作为Webpack参数,而不是npm配置参数
npm run build -- --colors
配置文件

下面是Webpack 2.1配置文件简要说明:

webpack.config.js
JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
path = require( "path" );
const config = {
    /**
     * 入口点,可以指定为字符串、对象或者数组
     */
    entry: "./app/entry", // 单入口点,注意相对于根目录需要 . 开头
    entry: [ "./app/entry1", "./app/entry2" ], // 多入口点
    entry: {
        a: "./app/entry-a",
        b: [ "./app/entry-b1", "./app/entry-b2" ]
    },  // 多入口点,键对Chunk进行命名,此名称对应output.filename的[name]
 
    /**
     * 输出,与Webpack生成的结果有关
     */
    output: {
        // 所有输出文件的Base目录
        path: path.resolve( __dirname, "build" ), // string
 
        // 入口点Chunk的文件名模板,Chunk是代码的分割,Bundle可以是单个文件,也可以被分割为很多Chunks
        filename: "bundle.js",  // 用于单入口点
        filename: "[name].js",  // 用于多入口点,name为入口点名称,默认名称main
        filename: "[chunkhash].js", // 用于长期缓存
        // 输出Base目录,相对于HTML页面的URL。此URL会在HTML、CSS等文件的URL中使用
        // 正确设置该目录,Webpack-dev-server需要用到
        publicPath: "/assets/",
        publicPath: "",
        publicPath: "https://cdn.example.com/",
 
        library: "MyLibrary", // 导出的库的名称
        // 库使用的模块系统
        libraryTarget: "umd", // enum
        libraryTarget: "umd-module", // ES2015 module wrapped in UMD
        libraryTarget: "commonjs-module", // ES2015 module wrapped in CommonJs
        libraryTarget: "commonjs2", // exported with module.exports
        libraryTarget: "commonjs", // exported as properties to exports
        libraryTarget: "amd", // defined with AMD defined method
        libraryTarget: "this", // property set on this
        libraryTarget: "var", // variable defined in root scope
    },
    // 模块选项
    module: {
        // 用于模块的规则,配置加载器,解析器选项,等等
        // 在Webpack 1.x中使用loaders而不是rules
        rules: [
            {
                // 包含的目录或者文件,使用正则式
                test: /\.jsx?$/,
                // 包含的目录或者文件,使用绝对路径
                include: [
                    path.resolve( __dirname, "app" )
                ],
                // 排除的目录或者文件,尽量避免使用
                exclude: [
                    path.resolve( __dirname, "app/demo-files" )
                ],
                issuer: { test, include, exclude },
                // 即使被覆盖,仍然执行此规则
                enforce: "pre",
                enforce: "post",
                // 应用到匹配文件的加载器。在Webpack2中不需要-loader后缀
                loader: "babel-loader",
                // 传递给加载器的配置项
                options: {
                    presets: [ "es2015" ]
                },
            },
            {
                test: "\.html$",
                // 应用多个加载器,可以使用别名loaders
                use: [
                    "htmllint-loader",
                    {
                        loader: "html-loader",
                        options: {}
                    }
                ],
                // 如果当前规则匹配,则下列规则中第一个匹配的规则被应用
                oneOf: [ /* rules */ ],
                // 如果当前规则匹配,则下列规则也被应用
                rules: [ /* rules */ ],
                // 仅当所有条件满足时才匹配
                resource: { and: [ /* conditions */ ] },
                // 当任意条件满足时即匹配
                resource: { or: [ /* conditions */ ] },
                // 当任意条件满足时即匹配
                resource: [ /* conditions */ ],
                // 当条件不满足时匹配
                resource: { not: /* condition */ },
            }
        ]
    },
 
    // 用于解析模块请求,这些配置不用于加载器的解析
    resolve: {
        // 从哪些目录寻找模块
        modules: [
            "node_modules",
            path.resolve( __dirname, "app" )
        ],
        // 模块的扩展名
        extensions: [ ".js", ".json", ".jsx", ".css" ],
        // 模块别名映射
        alias: {
            "module": "new-module",       // "module" -> "new-module"   "module/path/file" -> "new-module/path/file"
            "only-module$": "new-module", // "only-module" -> "new-module" 但是only-module目录下的文件不映射
        }
    },
 
    // 通过为浏览器的开发者工具添加元数据信息,增强调试
    devtool: "source-map", // enum
    devtool: "inline-source-map", // inlines SourceMap into orginal file
    devtool: "eval-source-map", // inlines SourceMap per module
    devtool: "hidden-source-map", // SourceMap without reference in original file
    devtool: "cheap-source-map", // cheap-variant of SourceMap without module mappings
    devtool: "cheap-module-source-map", // Webpack3 + Chrome 可以使用这个
    devtool: "eval",  // no SourceMap, but named modules. Fastest at the expense of detail
    // Webpack的Home目录,entry、module.rules.loader相对于此目录解析
    context: __dirname,
 
    // 此Bundle的运行平台
    target: "web", // enum
    target: "webworker", // WebWorker
    target: "node", // Node.js via require
    target: "async-node", // Node.js via fs and vm
    target: "node-webkit", // nw.js
    target: "electron-main", // electron, main process
    target: "electron-renderer", // electron, renderer process
 
    // 不去捆绑这些模块,而是在运行时动态加载
    externals: ["react", /^@angular\//],
    externals: "react", // string (exact match)
    externals: /^[a-z\-]+($|\/)/, // Regex
    externals: { // object
        angular: "this angular", // this["angular"]
        react: { // UMD
            commonjs: "react",
            commonjs2: "react",
            amd: "react",
            root: "React"
        }
    },
    externals: (request) => { /* ... */ return "commonjs " + request },
 
    // 插件列表
    plugins: [
        // ...
    ],
 
    // 收集构建时的时间消耗信息
    profile: true,
 
    // 启用或禁用缓存
    cache: false,
 
    // 启用或禁用监控文件的变动
    watch: true,
    // 监控选项
    watchOptions: {
        aggregateTimeout: 1000, // 把多个文件变更在一个rebuild中处理,超时时间
        poll: true,  // 使用文件系统轮询,对于不支持变化通知的系统,例如nfs,必须启用
        poll: 500, // 轮询间隔
    },
}
 
 
module.exports = config

配置的细节请参考官方文档。  

模块热替换

模块热替换(HMR - Hot Module Replacement)功能会在应用程序运行过程中替换、添加或删除模块,而无需重新加载整个页面。主要是通过以下几种方式,来显著加快开发速度:

  1. 保留在完全重新加载页面时丢失的应用程序状态
  2. 只更新变更内容,以节省宝贵的开发时间
  3. 调整样式更加快速 - 几乎相当于在浏览器调试器中更改样式

在应用程序中,HMR这样实现模块的置换:

  1. 应用程序代码要求 HMR runtime 检查更新
  2. HMR runtime(异步)下载更新,然后通知应用程序代码
  3. 应用程序代码要求 HMR runtime 应用更新
  4. HMR runtime(同步)应用更新

你可以设置 HMR,以使此进程自动触发更新,或者你可以选择要求在用户交互时进行更新。

在编译器中,除了普通资源,编译器(compiler)需要发出 "update",以允许更新之前的版本到新的版本。"update" 由两部分组成:

  1. 更新后的 manifest(JSON)
  2. 一个或多个更新后的 chunk (JavaScript)

manifest 包括新的编译 hash 和所有的待更新 chunk 目录。每个更新 chunk 都含有对应于此 chunk 的全部更新模块(或一个 flag 用于表明此模块要被移除)的代码。编译器确保模块 ID 和 chunk ID 在这些构建之间保持一致。通常将这些 ID 存储在内存中(例如,使用 webpack-dev-server 时),但是也可能将它们存储在一个 JSON 文件中。

从模块的视角来看,HMR 是可选功能,只会影响包含 HMR 代码的模块。例如:通过 style-loader 为 style 样式追加补丁。为了运行追加补丁,style-loader 实现了 HMR 接口;当它通过 HMR 接收到更新,它会使用新的样式替换旧的样式。

类似的,当在一个模块中实现了 HMR 接口,你可以描述出当模块被更新后发生了什么。然而在多数情况下,不需要强制在每个模块中写入 HMR 代码。如果一个模块没有 HMR 处理函数,更新就会冒泡(bubble up)。这意味着一个简单的处理函数能够对整个模块树(complete module tree)进行更新。如果在这个模块树中,一个单独的模块被更新,那么整组依赖模块都会被重新加载。

在开发过程中,可以将 HMR 作为 LiveReload 的替代。webpack-dev-server 支持 hot 模式,在试图重新加载整个页面之前,热模式会尝试使用 HMR 来更新。

命令行
webpack
命令格式
Shell
1
2
3
4
# entry,指定打包的入口点,可以使用<name>=<request>的形式指定多个入口点
# output,指定输出文件
# options,很多Webpack配置项被映射到命令行选项
webpack <entry> <output>  <options>
常用选项
选项 说明
-d 开发环境快捷选项,等价于:--debug --devtool source-map --output-pathinfo
-p 产品环境快捷选项,等价于:--optimize-minimize --optimize-occurrence-order
--watch 监控模式,监控所有依赖,当发生变化时自动重新编译
--config 指定Webpack配置文件
--progress 在stderr显示编译进度信息
--json 输出JSON格式,而非默认的供人阅读的格式
--colors 启用高亮
--no-color 禁用高亮
--sort-modules-by
--sort-chunks-by
--sort-assets-by
根据一个列来排序模块、块或者资产
--display-chunks 显示模块到块的划分
--display-reasons 显示一个模块被包含在结果中的原因
--display-error-details 显示关于错误的详细信息
--display-modules 显示隐藏模块。默认情况下如果模块位于node_modules", "bower_components", "jam", "components"等目录不会显示在命令输出中
--display-exclude 手工排除显示到输出中的模块
webpack-dev-server

该命令能够启动一个基于Express的服务器,以监控模式运行webpack。该命令由独立的包提供:

Shell
1
npm install webpack-dev-server  --save-dev

 webpack-dev-server能够自动监控源文件的变化,进行编译(编译的结果仅仅放置在内存,文件系统看不到),然后基于Sockjs发送到客户端,导致页面自动刷新。命令示例:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
webpack-dev-server
    --progress --colors
    # 监听IP和端口
    --port 8080 --host localhost  
    # 可以指定Web根目录,默认为当前目录
    --content-base build/          
    # 内联模式,一个简单的webpack-dev-server客户端被嵌入bundle,在代码变动时自动刷新
    # 也可以在webpack.config.js中指定: devServer: { inline: true }
    --inline    
    # 启用模块热替换                  
    --hot
    # 生成Sourcemap
    -d
关于IDE的Safe write

很多IDE或者编辑器支持所谓Safe write特性并且默认启用,这会导致Webpack dev server无法监控文件变化,需要关闭。

以Intellij Webstorm为例: Settings ⇨ A & B ⇨ System Settings,取消勾选:Use "safe write" ... 

Webpack 1.x
使用加载器

要使用加载器,首先需要安装它们。例如:

Shell
1
2
3
# css-loader能够读取CSS文件
# style-loader能够将CSS文件插入到页面中
npm install css-loader style-loader
编程式使用

要针对单个模块来链式调用加载器,可以参考如下语法:

JavaScript
1
2
3
4
5
6
7
8
// 使用!来分隔应用的加载器、模块名
require("!style!css!./style.css")
 
// 使用当前目录中loader.js定义的加载器来转换dir/file.txt文件
require("./loader!./dir/file.txt");
 
// bootstrap模块中的less目录中的bootstrap.less文件,将被less加载器转换,结果传递给css加载器,css加载器的结果传递给style加载器
require("!style!css!less!bootstrap/less/bootstrap.less");
配置式使用

上面的方式比较麻烦,最好在配置文件中声明文件Pattern对应的加载器:

webpack.config.js
JavaScript
1
2
3
4
5
6
7
8
module: {
    loaders: [
        // 对.css文件依次使用css、style加载器
        { test: /\.css$/, loader: 'style!css' }
        // 另一种写法
        { test: /\.css$/, loaders: ["style", "css"] },
    ]
}

然后,require语句就可以简化为:

JavaScript
1
require( './style.css' )
命令行使用

可以通过webpack命令行来指定加载器:

Shell
1
2
3
# 对于.jade文件使用jade加载器
# 对于.css文件,使用css、style加载器
webpack --module-bind jade --module-bind 'css=style!css'
代码分割

对于大型Web应用来说,将所有代码捆绑到一个文件中是很低效的,会大大延长客户端的加载耗时。特别是,某些代码仅仅在特定的情况下在会被用到,更不应该在应用初始化时立即加载。

Webpack支持把代码分割(code splitting)为多个块(Chunks),并按需加载这些块。其它绑定器可能把Chunk称为layers、rollups或者fragments。

代码分割是可选的特性,你可以在代码中定义分割点(split points),Webpack会处理依赖、输出文件以及运行时加载的代码。

除了按需加载这个基本特性外,代码分割还支持抽取多个入口点的共享代码,将其独立到共享块(shared chunk)中。

定义分割点

AMD和CommonJS规范分别定义了按需加载的方法,这些方法可以被Webpack自动识别为分割点。

CommonJS按需加载方法如下:

JavaScript
1
2
3
4
5
6
7
8
9
// 在callback执行之前,确保所有dependencies已经同步的加载
// 注意:require.ensure仅仅加载模块文件,但是不会执行其中的代码
require.ensure(dependencies, callback);
 
// 示例
require.ensure(["module-a", "module-b"], function() {
    // require则可能既加载文件,也执行模块代码
    var a = require("module-a");
});

AMD按需加载方法如下:

JavaScript
1
2
3
4
5
6
// 异步加载dependencies,全部完毕后,执行回调
require(dependencies, callback);
// require会加载模块并且执行其中的代码
require(["module-a", "module-b"], function(a, b) {
    // 回调的入参是加载模块的exports
});

Webpack本身不支持ES6模块系统,但是你可以使用Babel之类的编译器,将ES6 import语句编译为AMD或者CommonJS的require调用。

ES6 import仅仅用于静态依赖分析,这就导致它只能接受字符串直接量表示的模块标识符。这是个严重的缺点,因为按需加载的模块,其名称往往在运行时才能确定。

块的内容

所有在分割点引入的依赖,都会被分割到新的块中。如果这些依赖的代码本身也定义了分割点,则递归的生成新的块。

如果你在分割点回调函数中require新的依赖,则这些依赖会被捆绑到分割点生成的那个块中。 

块优化

如果两个Chunk包含相同的模块,则它们会被合并为单个Chunk。这样,合并后的块就会有多个父块(Parent chunks)。

很多插件参与块优化:LimitChunkCountPlugin、MinChunkSizePlugin、AggressiveMergingPlugin

块加载

根据Webpack配置文件的target属性的不同,相应的运行时加载Chunk的逻辑会被添加到bundle中。例如对于target=web,Chunk会通过jsonp加载。

每个Chunk只会被加载一次,并行的加载请求会被合并为单个请求。

块类型
入口点块 Entry chunk 这种块包含运行时代码、一系列的模块的代码。如果此块包含module 0则执行之,如果不包含module0则等待包含module0的块加载完毕然后执行之
普通块 Normal chunk

这种块仅仅包含一系列模块代码,其结构依赖于Chunk加载方式,例如通过jsonp加载时目标模块被包裹在jsonp回调函数中

这种块包含一个列表,存放它fullfill的其它Chunks的ID

初始块 Initial chunk 一种特殊的普通块。但是会和入口点块一样在应用初始化时加载。这种块会在使用CommonsChunkPlugin插件时出现
分割应用与框架代码

如果想把应用程序分割为app.js和vendor.js,你可以在vendor.js中require厂商的所有代码,然后使用CommonsChunkPlugin:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var webpack = require("webpack");
 
module.exports = {
    entry: {
        app: "./app.js",
        vendor: ["jquery", "underscore",
        ],
    },
    output: {
        filename: "bundle.js"
    }
    ,
    plugins: [
        new webpack.optimize.CommonsChunkPlugin(/* chunkName= */"vendor", /* filename= */"vendor.bundle.js")
    ]
};

 在HTML页面中需要同时引用:

XHTML
1
2
<script src="vendor.bundle.js"></script>
<script src="bundle.js"></script>

 

← 使用ESLint进行代码检查
CommonJS规范简介 →

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

Related Posts

  • 使用ESLint进行代码检查
  • JavaScript知识集锦
  • ExtJS 4中的选取器控件
  • ExtJS 4常用组件之表格
  • 定制ExtJS 4主题

Recent Posts

  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
  • A Comprehensive Study of Kotlin for Java Developers
  • 背诵营笔记
  • 利用LangChain和语言模型交互
  • 享学营笔记
ABOUT ME

汪震 | Alex Wong

江苏淮安人,现居北京。目前供职于腾讯云,专注容器方向。

GitHub:gmemcc

Git:git.gmem.cc

Email:gmemjunk@gmem.cc@me.com

ABOUT GMEM

绿色记忆是我的个人网站,域名gmem.cc中G是Green的简写,MEM是Memory的简写,CC则是我的小天使彩彩名字的简写。

我在这里记录自己的工作与生活,同时和大家分享一些编程方面的知识。

GMEM HISTORY
v2.00:微风
v1.03:单车旅行
v1.02:夏日版
v1.01:未完成
v0.10:彩虹天堂
v0.01:阳光海岸
MIRROR INFO
Meta
  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org
Recent Posts
  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
    In this blog post, I will walk ...
  • A Comprehensive Study of Kotlin for Java Developers
    Introduction Purpose of the Study Understanding the Mo ...
  • 背诵营笔记
    Day 1 Find Your Greatness 原文 Greatness. It’s just ...
  • 利用LangChain和语言模型交互
    LangChain是什么 从名字上可以看出来,LangChain可以用来构建自然语言处理能力的链条。它是一个库 ...
  • 享学营笔记
    Unit 1 At home Lesson 1 In the ...
  • K8S集群跨云迁移
    要将K8S集群从一个云服务商迁移到另外一个,需要解决以下问题: 各种K8S资源的迁移 工作负载所挂载的数 ...
  • Terraform快速参考
    简介 Terraform用于实现基础设施即代码(infrastructure as code)—— 通过代码( ...
  • 草缸2021
    经过四个多月的努力,我的小小荷兰景到达极致了状态。

  • 编写Kubernetes风格的APIServer
    背景 前段时间接到一个需求做一个工具,工具将在K8S中运行。需求很适合用控制器模式实现,很自然的就基于kube ...
  • 记录一次KeyDB缓慢的定位过程
    环境说明 运行环境 这个问题出现在一套搭建在虚拟机上的Kubernetes 1.18集群上。集群有三个节点: ...
  • eBPF学习笔记
    简介 BPF,即Berkeley Packet Filter,是一个古老的网络封包过滤机制。它允许从用户空间注 ...
  • IPVS模式下ClusterIP泄露宿主机端口的问题
    问题 在一个启用了IPVS模式kube-proxy的K8S集群中,运行着一个Docker Registry服务 ...
  • 念爷爷
      今天是爷爷的头七,十二月七日、阴历十月廿三中午,老人家与世长辞。   九月初,回家看望刚动完手术的爸爸,发

  • 6 杨梅坑

  • liuhuashan
    深圳人才公园的网红景点 —— 流花山

  • 1 2020年10月拈花湾

  • 内核缺陷触发的NodePort服务63秒延迟问题
    现象 我们有一个新创建的TKE 1.3.0集群,使用基于Galaxy + Flannel(VXLAN模式)的容 ...
  • Galaxy学习笔记
    简介 Galaxy是TKEStack的一个网络组件,支持为TKE集群提供Overlay/Underlay容器网 ...
TOPLINKS
  • Zitahli's blue 91 people like this
  • 梦中的婚礼 64 people like this
  • 汪静好 61 people like this
  • 那年我一岁 36 people like this
  • 为了爱 28 people like this
  • 小绿彩 26 people like this
  • 彩虹姐姐的笑脸 24 people like this
  • 杨梅坑 6 people like this
  • 亚龙湾之旅 1 people like this
  • 汪昌博 people like this
  • 2013年11月香山 10 people like this
  • 2013年7月秦皇岛 6 people like this
  • 2013年6月蓟县盘山 5 people like this
  • 2013年2月梅花山 2 people like this
  • 2013年淮阴自贡迎春灯会 3 people like this
  • 2012年镇江金山游 1 people like this
  • 2012年徽杭古道 9 people like this
  • 2011年清明节后扬州行 1 people like this
  • 2008年十一云龙公园 5 people like this
  • 2008年之秋忆 7 people like this
  • 老照片 13 people like this
  • 火一样的六月 16 people like this
  • 发黄的相片 3 people like this
  • Cesium学习笔记 90 people like this
  • IntelliJ IDEA知识集锦 59 people like this
  • Bazel学习笔记 38 people like this
  • 基于Kurento搭建WebRTC服务器 38 people like this
  • PhoneGap学习笔记 32 people like this
  • NaCl学习笔记 32 people like this
  • 使用Oracle Java Mission Control监控JVM运行状态 29 people like this
  • Ceph学习笔记 27 people like this
  • 基于Calico的CNI 27 people like this
Tag Cloud
ActiveMQ AspectJ CDT Ceph Chrome CNI Command Cordova Coroutine CXF Cygwin DNS Docker eBPF Eclipse ExtJS F7 FAQ Groovy Hibernate HTTP IntelliJ IO编程 IPVS JacksonJSON JMS JSON JVM K8S kernel LB libvirt Linux知识 Linux编程 LOG Maven MinGW Mock Monitoring Multimedia MVC MySQL netfs Netty Nginx NIO Node.js NoSQL Oracle PDT PHP Redis RPC Scheduler ServiceMesh SNMP Spring SSL svn Tomcat TSDB Ubuntu WebGL WebRTC WebService WebSocket wxWidgets XDebug XML XPath XRM ZooKeeper 亚龙湾 单元测试 学习笔记 实时处理 并发编程 彩姐 性能剖析 性能调优 文本处理 新特性 架构模式 系统编程 网络编程 视频监控 设计模式 远程调试 配置文件 齐塔莉
Recent Comments
  • qg on Istio中的透明代理问题
  • heao on 基于本地gRPC的Go插件系统
  • 黄豆豆 on Ginkgo学习笔记
  • cloud on OpenStack学习笔记
  • 5dragoncon on Cilium学习笔记
  • Archeb on 重温iptables
  • C/C++编程:WebSocketpp(Linux + Clion + boostAsio) – 源码巴士 on 基于C/C++的WebSocket库
  • jerbin on eBPF学习笔记
  • point on Istio中的透明代理问题
  • G on Istio中的透明代理问题
  • 绿色记忆:Go语言单元测试和仿冒 on Ginkgo学习笔记
  • point on Istio中的透明代理问题
  • 【Maven】maven插件开发实战 – IT汇 on Maven插件开发
  • chenlx on eBPF学习笔记
  • Alex on eBPF学习笔记
  • CFC4N on eBPF学习笔记
  • 李运田 on 念爷爷
  • yongman on 记录一次KeyDB缓慢的定位过程
  • Alex on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • haolipeng on 基于本地gRPC的Go插件系统
  • 吴杰 on 基于C/C++的WebSocket库
©2005-2025 Gmem.cc | Powered by WordPress | 京ICP备18007345号-2