We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
针对不同的环境,我们应对打包的要求也不一样:
同一套源码如何打包成适用不同环境的包,webpack 通过配置环境变量的值,帮我们实现了这一点:
方法一:
module.exports = { plugins: [ new webpack.DefinePlugin({ // 定义 NODE_ENV 环境变量为 production 'process.env': { NODE_ENV: JSON.stringify('production') } }), ], };
注意这里使用 JSON.stringify('production') ,而不直接使用 'production' ,原因是环境变量的值需要是一个由双引号包裹的字符串,而 JSON.stringify('production') 的值正好等于 '"production"' 。
JSON.stringify('production')
'production'
'"production"'
或:
方法二:
// 使用 NODE_ENV=production webpack ... 启动 module.exports = { plugins: [ new webpack.EnvironmentPlugin(['NODE_ENV']) ], };
在被打包的文件中可以测试一下:
if (process.env.NODE_ENV === 'production') { console.log('你正在使用线上环境'); } else { console.log('你正在使用开发环境'); }
注意: 只用在项目中使用到 process 时,webpack 才会将 process 模块的代码打包进来,没使用则不会。 并且打包成功的源码中 if (process.env.NODE_ENV === 'production') { console.log('你正在使用线上环境'); } else { console.log('你正在使用开发环境'); } 被直接替换成了 if (true) { console.log('你正在使用线上环境'); } else {} // 这里 console.log('你正在使用开发环境') 是死代码,在 UglifyJS 压缩时被去除了 此时访问 process 的语句被替换了而没有了,Webpack 也不会打包进 process 模块 定义的环境变量只对 Webpack 需要处理的代码有效,而不会影响 Node.js 运行时的环境变量的值。
注意:
只用在项目中使用到 process 时,webpack 才会将 process 模块的代码打包进来,没使用则不会。
process
并且打包成功的源码中
被直接替换成了
if (true) { console.log('你正在使用线上环境'); } else {} // 这里 console.log('你正在使用开发环境') 是死代码,在 UglifyJS 压缩时被去除了
此时访问 process 的语句被替换了而没有了,Webpack 也不会打包进 process 模块
定义的环境变量只对 Webpack 需要处理的代码有效,而不会影响 Node.js 运行时的环境变量的值。
process.env.NODE_ENV !== 'production' 中的 NODE_ENV 和 'production' 两个值是社区普遍的约定,很多第三方库(React)都针对此做了环境区分的优化,我们也开始使用这条判断语句在开发时区分开发环境和线上环境。
process.env.NODE_ENV !== 'production'
NODE_ENV
注意:在 webpack4 中 mode: 'production' 已经默认配置了process.env.NODE_ENV = 'production'
mode: 'production'
process.env.NODE_ENV = 'production'
优化用户体验不光要压缩代码文件,还要提高网络的传输速度,通过 CDN 可以实现。
CDN:内容分发网络,通过把资源部署到世界各地,用户在访问时按照就近原则从离用户最近的服务器获取资源,从而加速资源的获取速度。 CDN 其实是通过优化物理链路层传输过程中的网速有限、丢包等问题来提升网速的,其大致原理可以如下:
因为 CDN 都有缓存,所以为了避免 CDN 缓存导致用户加载到老版本的问题,需要遵循以下规则:
所以针对以上,我们在构建中注意:
const path = require('path'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); const {WebPlugin} = require('web-webpack-plugin'); module.exports = { // 省略 entry 配置... output: { // 给输出的 JavaScript 文件名称加上 Hash 值 filename: '[name]_[chunkhash:8].js', path: path.resolve(__dirname, './dist'), // 指定存放 JavaScript 文件的 CDN 目录 URL publicPath: '//js.cdn.com/id/', }, module: { rules: [ { // 增加对 CSS 文件的支持 test: /\.css$/, // 提取出 Chunk 中的 CSS 代码到单独的文件中 use: ExtractTextPlugin.extract({ // 压缩 CSS 代码 use: ['css-loader?minimize'], // 指定存放 CSS 中导入的资源(例如图片)的 CDN 目录 URL publicPath: '//img.cdn.com/id/' }), }, { // 增加对 PNG 文件的支持 test: /\.png$/, // 给输出的 PNG 文件名称加上 Hash 值 use: ['file-loader?name=[name]_[hash:8].[ext]'], }, // 省略其它 Loader 配置... ] }, plugins: [ // 使用 WebPlugin 自动生成 HTML new WebPlugin({ // HTML 模版文件所在的文件路径 template: './template.html', // 输出的 HTML 的文件名称 filename: 'index.html', // 指定存放 CSS 文件的 CDN 目录 URL stylePublicPath: '//css.cdn.com/id/', }), new ExtractTextPlugin({ // 给输出的 CSS 文件名称加上 Hash 值 filename: `[name]_[contenthash:8].css`, }), // 省略代码压缩插件配置... ], };
<link rel="dns-prefetch" href="//js.cdn.com">
//cdn.com/
http:
https:
Tree Shaking 可以用来剔除 JavaScript 中用不上的死代码。但仅仅针对的是 ES6 语法的代码。这是因为 ES6 模块化语法是静态的(import x from './util'; :导入导出的都是静态的字符串),webpack 可以简单的分析出哪些被 import 或 export 了,如果采用 ES5 ( require(x+y) ),webpack 则无法分析出具体哪些可以剔除。
import x from './util';
import
export
require(x+y)
目前的 Tree Shaking 还有些的局限性,经实验发现: 不会对entry入口文件做 Tree Shaking。 不会对异步分割出去的代码做 Tree Shaking。
目前的 Tree Shaking 还有些的局限性,经实验发现:
步骤一:配置 .babelrc
.babelrc
{ "presets": [ [ "env", { // 关闭 Babel 的模块转换功能,保留原本的 ES6 模块化语法 "modules": false } ] ] }
并且在启动 Webpack 时带上 --display-used-exports 参数,以方便追踪 Tree Shaking 的工作。
--display-used-exports
注意:Webpack 只是指出了哪些函数用没用上,要剔除用不上的代码还得经过 UglifyJS 去处理一遍
步骤二:配合 UglifyJS
这里可以简单操作一下:在启动 Webpack 时带上 --optimize-minimize 参数,
--optimize-minimize
具体的操作可以看一下 UglifyJS 模块。
为什么要提取公共代码?
很多大型项目都是由多页面构成,并且所有的页面都采用同一套技术及基础库,如果每个页面单独打包、加载,就会导致每个包都包含大量的公共部分、基础库。影响用户体验:
所以我们需要提取公共代码,单独打包,当用户加载多页面应用时,第一次访问的时候,公共代码将会被浏览器缓存起来,当加载其它页面时,用户不需要再重复加载公共模块,直接从缓存中获取即可。
怎么提取喃?
前面我们提到我们可以使用 DllPlugin 来提取基础模块库,这里不再赘述。
**方式一:CommonsChunkPlugin **
Webpack 内置了专门用于提取多个 Chunk 中公共部分的插件 CommonsChunkPlugin:
new webpack.optimize.CommonsChunkPlugin({ // 从哪些 Chunk 中提取 // chunks: ['a', 'b'], 不填则默认会从所有已知的 Chunk 中提取 // name: 提取出的公共部分形成一个新的 Chunk,这个新 Chunk 的名称 name: "index", // 在传入 公共chunk(commons chunk) 之前所需要包含的最少数量的 chunks 。 // 数量必须大于等于2,或者少于等于 chunks的数量 // 传入 `Infinity` 会马上生成 公共chunk,但里面没有模块。 // 你可以传入一个 `function` ,以添加定制的逻辑(默认是 chunk 的数量) minChunks: 2, // 如果设置为 `true`,所有公共 chunk 的子模块都会被选择 children: true, // 如果设置为 `true`,所有公共 chunk 的后代模块都会被选择 deepChildren: true, })
方式二:SplitChunksPlugin(webpack4以上)
new webpack.optimize.SplitChunksPlugin({ chunks: "all", minSize: 30000, minChunks: 1, maxAsyncRequests: 5, maxInitialRequests: 3, automaticNameDelimiter: '~', name: true, cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, priority: -10 }, default: { minChunks: 2, priority: -20, reuseExistingChunk: true } } }),
针对单页面应用首屏加载过慢,我们也可以采用懒加载、按需加载的方式控制首屏加载文件大小。
我们知道在 ES6 中,我们使用 import 、export 静态的加载、导出文件,这里以ES6 import() 为例,更多可查看 Module Methods。
import()
步骤一:按需加载
export default class Routes extends React.Component { render() { return ( <Router path={href} history={browserHistory}> <Switch> <Route exact path='/' component={Splash} /> <Route path='/login' component={Login} /> <Route path='/main/' component={getAsyncComponent( // 异步加载函数,异步地加载 main 组件 () => import(/* webpackChunkName: 'page-main' */'./page/main') )} /> </Switch> </Router> ) } } /** * 异步加载组件 * @param load 组件加载函数,load 函数会返回一个 Promise,在文件加载完成时 resolve * @returns {AsyncComponent} 返回一个高阶组件用于封装需要异步加载的组件 */ function getAsyncComponent(load) { return class AsyncComponent extends React.PureComponent { componentDidMount() { // 在高阶组件 DidMount 时才去执行网络加载步骤 load().then(({default: component}) => { // 代码加载成功,获取到了代码导出的值,调用 setState 通知高阶组件重新渲染子组件 this.setState({ component, }) }); } render() { const {component} = this.state || {}; // component 是 React.Component 类型,需要通过 React.createElement 生产一个组件实例 return component ? React.createElement(component) : null; } } }
步骤二:支持 import()
你可能遇到 import() 报错 Support for the experimental syntax 'dynamicImport' isn't currently enabled ,这是因为 import() 还没有被加入到 ECMAScript 标准中去,所以我们需要安装 npm install babel-plugin-syntax-dynamic-import --save-dev ,并且配置 :
Support for the experimental syntax 'dynamicImport' isn't currently enabled
npm install babel-plugin-syntax-dynamic-import --save-dev
// .babelrc 文件 { "presets": [ // ... ], "plugins": [ "syntax-dynamic-import" // ... ] }
并且可以在 webapck 中配置为动态加载的 Chunk 配置输出文件的名称 chunkFilename 。
chunkFilename
The text was updated successfully, but these errors were encountered:
No branches or pull requests
一、区分环境
针对不同的环境,我们应对打包的要求也不一样:
同一套源码如何打包成适用不同环境的包,webpack 通过配置环境变量的值,帮我们实现了这一点:
配置很简单:
方法一:
注意这里使用
JSON.stringify('production')
,而不直接使用'production'
,原因是环境变量的值需要是一个由双引号包裹的字符串,而JSON.stringify('production')
的值正好等于'"production"'
。或:
方法二:
使用也很简单:
在被打包的文件中可以测试一下:
process.env.NODE_ENV !== 'production'
中的NODE_ENV
和'production'
两个值是社区普遍的约定,很多第三方库(React)都针对此做了环境区分的优化,我们也开始使用这条判断语句在开发时区分开发环境和线上环境。注意:在 webpack4 中
mode: 'production'
已经默认配置了process.env.NODE_ENV = 'production'
二、CDN 加速
优化用户体验不光要压缩代码文件,还要提高网络的传输速度,通过 CDN 可以实现。
因为 CDN 都有缓存,所以为了避免 CDN 缓存导致用户加载到老版本的问题,需要遵循以下规则:
所以针对以上,我们在构建中注意:
注意:
<link rel="dns-prefetch" href="//js.cdn.com">
去预解析域名,降低域名解析带来的延迟//cdn.com/
的URL省略掉了前面的http:
或者https:
前缀,在具体数据请求时,它会根据当前 HTML 的 URL 加载模式去确定是采用 HTTP 还是 HTTPS 模式。三、使用 Tree Shaking
Tree Shaking 可以用来剔除 JavaScript 中用不上的死代码。但仅仅针对的是 ES6 语法的代码。这是因为 ES6 模块化语法是静态的(
import x from './util';
:导入导出的都是静态的字符串),webpack 可以简单的分析出哪些被import
或export
了,如果采用 ES5 (require(x+y)
),webpack 则无法分析出具体哪些可以剔除。步骤一:配置
.babelrc
并且在启动 Webpack 时带上
--display-used-exports
参数,以方便追踪 Tree Shaking 的工作。注意:Webpack 只是指出了哪些函数用没用上,要剔除用不上的代码还得经过 UglifyJS 去处理一遍
步骤二:配合 UglifyJS
这里可以简单操作一下:在启动 Webpack 时带上
--optimize-minimize
参数,具体的操作可以看一下 UglifyJS 模块。
四 、提取公共代码
为什么要提取公共代码?
很多大型项目都是由多页面构成,并且所有的页面都采用同一套技术及基础库,如果每个页面单独打包、加载,就会导致每个包都包含大量的公共部分、基础库。影响用户体验:
所以我们需要提取公共代码,单独打包,当用户加载多页面应用时,第一次访问的时候,公共代码将会被浏览器缓存起来,当加载其它页面时,用户不需要再重复加载公共模块,直接从缓存中获取即可。
怎么提取喃?
第一步:使用 DllPlugin 来提取基础模块库,预构建依赖包
前面我们提到我们可以使用 DllPlugin 来提取基础模块库,这里不再赘述。
第二步:使用 CommonsChunkPlugin 或 SplitChunksPlugin(webpack4以上) 对公共模块打包
**方式一:CommonsChunkPlugin **
Webpack 内置了专门用于提取多个 Chunk 中公共部分的插件 CommonsChunkPlugin:
方式二:SplitChunksPlugin(webpack4以上)
五、按需加载
针对单页面应用首屏加载过慢,我们也可以采用懒加载、按需加载的方式控制首屏加载文件大小。
我们知道在 ES6 中,我们使用
import
、export
静态的加载、导出文件,这里以ES6import()
为例,更多可查看 Module Methods。步骤一:按需加载
步骤二:支持 import()
你可能遇到
import()
报错Support for the experimental syntax 'dynamicImport' isn't currently enabled
,这是因为import()
还没有被加入到 ECMAScript 标准中去,所以我们需要安装npm install babel-plugin-syntax-dynamic-import --save-dev
,并且配置 :并且可以在 webapck 中配置为动态加载的 Chunk 配置输出文件的名称
chunkFilename
。The text was updated successfully, but these errors were encountered: