[web] webpack (3)
CommonsChunkPlugin
默认情况下CommonsChunkPlugin可以将多entry的公共依赖模块提取到一个chunk文件。
实际上如果不是多entry,在同一entry系列里的重复引入模块,是不会被webpack重复打包的。
第二个默认行为是如果不是多入口且没有指定打包的库,会打包manifest。
使用CommonsChunkPlugin更主要是为了提取较稳定的库,使不必每次都重复打包,提升编译效率。在生产上可以减少文件的重新下载。
CommonsChunkPlugin的filename默认规则是与output.filename一致的。
分别提取公共库
entry: {
index: './src/index.js',
vendor1: ['lodash'],
vendor2: ['underscore']
},
new webpack.optimize.CommonsChunkPlugin({
name: ['vendor1', 'vendor2']
})
minChunks ?
在传入公共chunk(commons chunk) 之前所需要包含的最少数量的 chunks 。
数量必须大于等于2,或者少于等于 chunks的数量
传入 Infinity
会马上生成 公共chunk,但里面没有模块。
可以传入一个 function
,以添加定制的逻辑(默认是 chunk 的数量)
chunks
通过 chunk name 去选择 chunks 的来源。
chunk 必须是 公共chunk 的子模块。
如果被忽略,所有的,所有的 入口chunk (entry chunk) 都会被选择。
children
似乎默认是true?
设为 true 时,指定 source chunks 为 children of commons chunk。可以认为是 entry chunks 通过 code split 创建的 children chunks。children 与 chunks不可同时设置(它们都是指定 source chunks 的)。
children 可以用来把 entry chunk 创建的 children chunks 的共用模块合并到自身,但这会导致初始加载时间较长.
async
即解决children: true时合并到 entry chunks 自身时初始加载时间过长的问题。async 设为 true 时,commons chunk 将不会合并到自身,而是使用一个新的异步的 commons chunk。当这个 commons chunk 被下载时,自动并行下载相应的共用模块。
如果设置为 true
,一个异步的 公共chunk 会作为 options.name
的子模块,和 options.chunks
的兄弟模块被创建。
它会与 options.chunks
并行被加载。
Instead of using option.filename
, it is possible to change the name of the output file by providing
the desired string here instead of true
.
deepChildren: boolean,
// If `true` all descendants of the commons chunk are selected
minSize: number,
// 在 公共chunk 被创建立之前,所有 公共模块 (common module) 的最少大小。
这种写法似乎提取不到children里的公共chunk,也可能是默认行为。
如果直接写vendor配置是可以提取到的。
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
minChunks: (module) => {
return module.context && module.context.includes("node_modules");
}
}),
管理依赖
require.context
require.context(directory, useSubdirectories = false, regExp = /^\.\//)
3个参数分别为:
- 要搜索的文件夹目录
- 是否还应该搜索它的子目录
- 一个匹配文件的正则表达式
传递给 require.context 的参数必须是字面量(literal)
// (创建了)一个包含了 test 文件夹(不包含子目录)下面的、所有文件名以 `.test.js` 结尾的、能被 require 请求到的文件的上下文。
require.context("./test", false, /\.test\.js$/);
// (创建了)一个包含了父级文件夹(包含子目录)下面,所有文件名以 `.stories.js` 结尾的文件的上下文。
require.context("../", true, /\.stories\.js$/);
导出的方法有 3 个属性: resolve, keys, id。
- resolve 是一个函数,它返回请求被解析后得到的模块 id。
- keys 也是一个函数,它返回一个数组,由所有可能被上下文模块处理的请求组成。
- id 是上下文模块里面所包含的模块 id. 它可能在使用 module.hot.accept 的时候被用到。
function importAll (r) {
r.keys().forEach(r);
}
importAll(require.context('../components/', true, /\.js$/));
// 在构建时,所有被 require 的模块都会被存到(上面代码中的)cache 里面。
var cache = {};
function importAll (r) {
r.keys().forEach(key => cache[key] = r(key));
}
importAll(require.context('../components/', true, /\.js$/));
webpack-dev-server
CLI only
webpack-dev-server --color
webpack-dev-server --progress
//一切服务都启用gzip 压缩
compress: true
//当使用 HTML5 History API 时,任意的 404 响应都可能需要被替代为 index.html。一般需要与publicPath一起用。
historyApiFallback: true
//默认是 localhost。如果希望服务器外部可访问,指定如下:
host: "0.0.0.0"
//启用 webpack 的模块热替换特性:
hot: true
//默认情况下,dev-server 通过 HTTP 提供服务。也可以选择带有 HTTPS 的 HTTP/2 提供服务:
https: true
//自动打开浏览器
open: true
//Usage via the CLI
webpack-dev-server --open
//代理:
proxy: {
"/api": "http://localhost:3000"
}
publicPath路径下的打包文件可在浏览器中访问。
假设服务器运行在 http://localhost:8080
并且 output.filename 被设置为 bundle.js。默认 publicPath 是 "/",所以包(bundle)可以通过 http://localhost:8080/bundle.js
访问。
可以修改 publicPath,将 bundle 放在一个目录:
publicPath: "/assets/"
现在可以通过 http://localhost:8080/assets/bundle.js
访问。
contentBase告诉服务器从哪里提供内容。
默认情况下,将使用当前工作目录作为提供内容的目录,但是可以修改为其他目录。
publicPath优先级比contentBase高。
contentBase: path.join(__dirname, "public")
stats 选项能准确地控制显示哪些包的信息。
output
//output 目录对应一个绝对路径。
path: path.resolve(__dirname, 'dist/assets')
//配置如何暴露 library。
libraryTarget:"var"(默认值)
publicPath
publicPath相当于在输出文件前面加前缀(prefix)
对于按需加载(on-demand-load)或加载外部资源(external resources)(如图片、文件等)来说,output.publicPath 是很重要的选项。如果指定了一个错误的值,则在加载这些资源时会收到 404 错误。
此选项指定在浏览器中所引用的「此输出目录对应的公开 URL」。相对 URL(relative URL) 会被相对于 HTML 页面(或 标签)解析。相对于服务的 URL(Server-relative URL),相对于协议的 URL(protocol-relative URL) 或绝对 URL(absolute URL) 也可是可能用到的,或者有时必须用到,例如:当将资源托管到 CDN 时。
该选项的值是以 runtime(运行时) 或 loader(载入时) 所创建的每个 URL 为前缀。因此,在多数情况下,此选项的值都会以/结束。
默认值是一个空字符串 ""。
对于一个 chunk 请求,看起来像这样 /assets/4.chunk.js。
publicPath: "/assets/",
chunkFilename: "[id].chunk.js"
webpack-dev-server 也会默认从 publicPath 为基准,使用它来决定在哪个目录下启用服务,来访问 webpack 输出的文件。
在编译时(compile time)无法知道输出文件的 publicPath 的情况下,可以留空,然后在入口文件(entry file)处使用自由变量(free variable) __webpack_public_path__
,以便在运行时(runtime)进行动态设置。
__webpack_public_path__ = myRuntimePublicPath
sourceMapFilename
此选项会向硬盘写入一个输出文件,只在 devtool 启用了 SourceMap 选项时才使用。
配置 source map 的命名方式。默认使用 "[file].map"。
chunkFilename
此选项决定了非入口(non-entry) chunk 文件的名称。
一般与代码分离和懒加载一起使用。
注意这些文件名需要在 runtime 根据 chunk 发送的请求去生成。因此,需要在 webpack runtime 输出 bundle 值时,将 chunk id 的值对应映射到占位符(如 [name] 和 [chunkhash])。这会增加文件大小,并且在任何 chunk 的占位符值修改后,都会使 bundle 失效。
默认使用 [id].js 或从 output.filename 中推断出的值([name] 会被预先替换为 [id] 或 [id].)
import()
import('path/to/module') -> Promise
动态地加载模块。调用 import() 之处,被作为分离的模块起点,意思是,被请求的模块和它引用的所有子模块,会分离到一个单独的 chunk 中。
ES2015 loader 规范 定义了 import() 方法,可以在运行时动态地加载 ES2015 模块。
if ( module.hot ) {
import('lodash').then(_ => {
// Do something with lodash (a.k.a '_')...
})
}
import 规范不允许控制模块的名称或其他属性,因为 "chunks" 只是 webpack 中的一个概念。幸运的是,webpack 中可以通过注释接收一些特殊的参数,而无须破坏规定:
import(
/* webpackChunkName: "my-chunk-name" */
/* webpackMode: "lazy" */
'module'
);
//这两个选项可以组合起来使用
/* webpackMode: "lazy-once", webpackChunkName: "all-i18n-data" */
webpackChunkName
新 chunk 的名称。[index] and [request] 占位符,分别支持赋予一个递增的数字和实际解析的文件名。
webpackMode
指定以不同的模式解析动态导入。
支持以下选项:
- "lazy"(默认):为每个 import() 导入的模块,生成一个可延迟加载(lazy-loadable) chunk。
- "lazy-once":生成一个可以满足所有 import() 调用的单个可延迟加载(lazy-loadable) chunk。此 chunk 将在第一次 import() 调用时获取,随后的 import() 调用将使用相同的网络响应。注意,这种模式仅在部分动态语句中有意义,例如 import(
./locales/${language}.json
),其中可能含有多个被请求的模块路径。 - "eager":不会生成额外的 chunk,所有模块都被当前 chunk 引入,并且没有额外的网络请求。仍然会返回 Promise,但是是 resolved 状态。和静态导入相对比,在调用 import()完成之前,该模块不会被执行。
- "weak":尝试加载模块,如果该模块函数已经以其他方式加载(即,另一个 chunk 导入过此模块,或包含模块的脚本被加载)。仍然会返回 Promise,但是只有在客户端上已经有该 chunk 时才成功解析。如果该模块不可用,Promise 将会是 rejected 状态,并且网络请求永远不会执行。当需要的 chunks 始终在(嵌入在页面中的)初始请求中手动提供,而不是在应用程序导航在最初没有提供的模块导入的情况触发,这对于通用渲染(SSR)是非常有用的。
慎用变量
完全动态的语句(如 import(foo)),因为 webpack 至少需要一些文件的路径信息,而 foo 可能是系统或项目中任何文件的任何路径,因此 foo 将会解析失败。import() 必须至少包含模块位于何处的路径信息,所以打包应当限制在一个指定目录或一组文件中。
调用 import() 时,包含在其中的动态表达式 request,会潜在的请求的每个模块。例如,import(./locale/${language}.json
) 会导致 ./locale 目录下的每个 .json 文件,都被打包到新的 chunk 中。在运行时,当计算出变量 language 时,任何文件(如 english.json 或 german.json)都可能会被用到。
css-loader
如果同时使用了css-loader和postcss-loader
css-loader开启了sourceMap.
postcss-loader也需要开启sourceMap
babel
前端项目用babel-polyfill。
类库开发用babel-plugin-transform-runtime。
ModuleConcatenationPlugin
new webpack.optimize.ModuleConcatenationPlugin()
用于作用域提升(Scope Hoisting),但是只在ES6 module里有效.
作用域提升作用:
- 文件体积比之前更小。
- 运行代码时创建的函数作用域也比之前少了,开销也随之变小。
运行 Webpack 时加上 --display-optimization-bailout
参数可以得知为什么项目无法使用 Scope Hoisting.
uglifyjs-webpack-plugin
uglifyjs-webpack-plugin因为无法正确识别babel转译过的代码,导致tree shaking 效果不好,可以考虑用babel-minify-webpack-plugin或webpack-closure-compiler替换.
happypack
vue-markdown-loader不支持happypack.
实践效果感觉几乎没有,有时甚至是负面效果,不知是否是配置问题.
使用时会产生一个.happy的文体夹存放cache,最近这样用有印象的是parcel.
优化
对脚本文件应用 [chunkhash]
对 extractTextPlugin 应用的的文件应用 [contenthash]
;
使用 CommonsChunkPlugin 合理抽出公共库 vendor(包含社区工具库这些 如 lodash), 如果必要也可以抽取业务公共库 common(公共部分的业务逻辑),以及 webpack的 runtime;
在开发环境下使用 NamedModulesPlugin 来固化 module id,在生产环境下使用 HashedModuleIdsPlugin 来固化 module id
使用 NamedChunksPlugin 来固化 runtime 内以及在使用动态加载时分离出的 chunk 的 chunk id。
其他
{
"presets": [
["env", {
modules: false,
...
}]
]
}
在有webpack时关闭babel对modules 的处理.
可以更好的tree-shaking?
可以解决export *
报错的问题?
webpack-closure-compiler 似乎对 export *
还有 import()
有识别问题