《晋级篇(续):复杂项目下的代码分割》

    1、使用extract-text-webpack-plugin打包多个css文件;
    2、CommonsChunkPlugin:抽取公共模块;
    3、ProvidePlugin:全局调用某模块;
    4、require.ensure():按需加载模块。

    咱们还是以lesson4的demo为准,把lesson4的src开发目录复制到lesson5下,这一次咱们把项目搞得相对复杂一些,虽说现在比较成熟的前端团队都会有自己的ui库,为了方便咱们还是从成熟的bootstrap和font-awesome来切入,需要注意的是要处理好jquery和bootstrap的依赖关系。
    首先把index.html中的

    删除,然后npm安装jquery、bootstrap和font-awesome

    copy以下代码到webpack.config.js中

    1. const path = require('path'),
    2. HtmlWebpackPlugin = require('html-webpack-plugin'),
    3. webpack = require('webpack'),
    4. ExtractTextPlugin = require("extract-text-webpack-plugin"),
    5. OpenBrowserPlugin = require('open-browser-webpack-plugin'),
    6. extractVendor = new ExtractTextPlugin('vendor.css'),
    7. // 抽取bootstrap和font-awesome公共样式
    8. extractStyle = new ExtractTextPlugin('style.css'); // 抽取自定义样式
    9. module.exports = {
    10. entry: process.env.NODE_ENV === 'production' ? {
    11. vendor: ['jquery', 'bootJs'],
    12. app: './webpack.entry'
    13. }: [
    14. 'webpack-dev-server/client?http://localhost:8080',
    15. 'webpack/hot/only-dev-server',
    16. './webpack.entry.js'
    17. ],
    18. output: {
    19. filename: 'bundle.[hash].js',
    20. path: path.resolve(__dirname, './build'),
    21. publicPath: ''
    22. },
    23. context: __dirname,
    24. module: {
    25. rules: [{
    26. test: /\.css/,
    27. use: process.env.NODE_ENV === 'production' ? extractVendor.extract({
    28. fallback: "style-loader",
    29. use: "css-loader?minimize=true"
    30. }) : ['style-loader', 'css-loader?sourceMap']
    31. },
    32. {
    33. test: /\.scss$/,
    34. use: process.env.NODE_ENV === 'production' ? extractStyle.extract({
    35. fallback: "style-loader",
    36. use: ["css-loader", "sass-loader"]
    37. }) : ['style-loader', 'css-loader?sourceMap', 'sass-loader?sourceMap']
    38. },
    39. {
    40. test: /\.(jpg|png)$/,
    41. use: ['url-loader?limit=10000&name=img/[name].[ext]']
    42. },
    43. {
    44. test: /\.html$/,
    45. use: 'html-loader?interpolate=require'
    46. },
    47. {
    48. test: /\.js$/,
    49. exclude: /node_modules/,
    50. use: {
    51. loader: 'babel-loader',
    52. options: {
    53. presets: ['env']
    54. }
    55. }
    56. },
    57. {
    58. test: /\.(eot|svg|ttf|woff|woff2)(\?\S*)?$/,
    59. use: ['file-loader?name=fonts/[name].[ext]']
    60. }]
    61. },
    62. new HtmlWebpackPlugin({
    63. template: './src/index.html',
    64. filename: 'index.html'
    65. }),
    66. extractStyle,
    67. new webpack.DefinePlugin({
    68. 'NODE_ENV': JSON.stringify(process.env.NODE_ENV)
    69. }),
    70. new webpack.optimize.UglifyJsPlugin({
    71. compress: {
    72. warnings: true
    73. }
    74. }),
    75. // CommonsChunkPlugin可以让我们在几个模块之间抽取出公共部分内容,并且把他们添加到公共的打包模块中
    76. new webpack.optimize.CommonsChunkPlugin({
    77. name: "vendor",
    78. // 模块名
    79. filename: "vendor.js",
    80. // 文件名
    81. minChunks: Infinity,
    82. // 该模块至少被其他模块调用多少次时,才会被打包到公共模块中,这个数字必须大于等于2,当传入Infinity时会马上生成
    83. }),
    84. // ProvidePlugin可以全局引入某个模块,在其他模块不需要再手动引入且可以直接调用,也能解决其他第三方库(像bootstrap)对jquery的依赖
    85. new webpack.ProvidePlugin({
    86. $: 'jquery',
    87. // $ 是jquery的模块输出对象,下面的jQuery也是一样的,在其他模块中可以直接被调用
    88. jQuery: 'jquery'
    89. })
    90. ] : [
    91. new HtmlWebpackPlugin({
    92. template: './src/index.html',
    93. filename: 'index.html'
    94. }),
    95. new webpack.HotModuleReplacementPlugin(),
    96. new webpack.NamedModulesPlugin(),
    97. new webpack.DefinePlugin({
    98. 'NODE_ENV': JSON.stringify(process.env.NODE_ENV)
    99. }),
    100. new OpenBrowserPlugin({
    101. url: 'http://localhost:8080/'
    102. }),
    103. new webpack.ProvidePlugin({
    104. $: 'jquery',
    105. jQuery: 'jquery'
    106. })
    107. ],
    108. devServer: {
    109. contentBase: path.resolve(__dirname, 'src'),
    110. hot: true,
    111. noInfo: false
    112. },
    113. devtool: 'source-map',
    114. resolve: {
    115. extensions: ['.js', '.scss', '.html'],
    116. alias: {
    117. 'jquery': 'jquery/dist/jquery.min.js',
    118. 'bootCss': 'bootstrap/dist/css/bootstrap.css',
    119. 'bootJs': 'bootstrap/dist/js/bootstrap.js',
    120. 'fontAwesome': 'font-awesome/css/font-awesome.css'
    121. }
    122. }
    123. };
    1. import "bootCss";
    2. import "fontAwesome";
    3. const cssAndJsContext = require.context('./src', true, /\.(js|scss)$/i);
    4. cssAndJsContext.keys().forEach((key) => {
    5. cssAndJsContext(key);
    6. if (NODE_ENV === 'development') {
    7. const htmlContext = require.context('./src', true, /\.html$/i);
    8. htmlContext.keys().forEach((key) => {
    9. htmlContext(key);
    10. });
    11. }

    运行npm build命令,打包后文件如下:

    jquery和bootstrap.js被打包在vendor.js,bootstrap.css和font-awesome.css被打包在vendor.css中,本地打开index.html文件,页面显示正常。于此,咱们解决了第一个问题“第三方库的引入”。
    随着项目体积越来越大,有些文件我们不需要页面初始化的时候就加载进来,而应该是当用户发生某个操作之后,按需加载。这里我们就需要用到require.ensure()
    在webpack.config.js文件中的output属性增加一项参数:chunkFilename,copy以下代码到webpack.config.js

    copy以下代码到webpack.entry.js

    1. import "bootCss";
    2. import "fontAwesome";
    3. import "bootJs";
    4. const cssAndJsContext = require.context('./src', true, /[^\/][^abc]\.(js|scss)$/i); // 修改了正则表达式,使a.js,b.js,c.js不被引入
    5. cssAndJsContext.keys().forEach((key) => {
    6. cssAndJsContext(key);
    7. });
    8. if (NODE_ENV === 'development') {
    9. const htmlContext = require.context('./src', true, /\.html$/i);
    10. htmlContext.keys().forEach((key) => {
    11. htmlContext(key);
    12. });
    13. }

    在src目录下新建public文件夹,并创建a.js,b.js,c.js三个文件

    1. cd src && mkdir public && cd public
    2. touch a.js b.js c.js

    a.js

    1. console.log("I am A");

    c.js

    1. console.log("I am C");

    再修改下body.html

    1. <h1 class="body-title">this is body</h1>
    2. <i class="fa fa-cog fa-spin fa-3x fa-fw"></i>
    3. <ul class="body-list">
    4. <li class="body-list-item" id="body-input">你可以使用BannerPlugin给你的每个打包文件加上你的签名<br>webpack教程<br>by kingvid</li>
    5. </ul>
    6. <button id="body-btn">点我</button>

    以及修改下body.js

    1. // 这里不再需要再import或require jquery,在webpack.config.js中新增了externals属性,让jquery可以在webpack整个运行环境中被调用
    2. var element = $("#body-input"),
    3. str = element.html(),
    4. progress = 0,
    5. timer = setInterval(() => {
    6. let current = str.substr(progress, 1);
    7. if (current == '<') {
    8. progress = str.indexOf('>', progress) + 1;
    9. } else {
    10. progress++;
    11. }
    12. element.html(str.substring(0, progress) + (progress && 1 ? '_': ''));
    13. if (progress >= str.length) {
    14. clearInterval(timer);
    15. element.html(str.substring(0, progress));
    16. }
    17. },150);
    18. require('../../public/a.js'); // 这里会立即执行,会被打包到bundle.js文件中
    19. $("#body-btn").click(() => {
    20. // require.ensure(dependencies: String[], callback: function(require), chunkName: String)
    21. // dependencies:在执行之前加载完模块依赖
    22. // callback:模块依赖加载完全之后执行该回调函数,require函数传入该回调函数中,供函数内部调用
    23. // chunkName:webpack打包该模块时的生成的文件命名,当有多个require.ensure()使用相同的chunkname时,webpack会把它们统一打包到一个文件中,如果chunkName为空,传回模块id
    24. require.ensure(['../../public/b.js'],
    25. function(require) {
    26. require('../../public/c.js');
    27. // 注意b.js在这里是不会被执行的,它只是被加载了,如果要调用的话,需要执行`require('../../public/b.js')`
    28. },'bc');

    运行npm start,效果如下:
    《晋级篇(续):复杂项目下的代码分割》 - 图1
    可以看到a.js一开始就被执行了,c.js知道按钮被点击之后才被加载和执行,b.js只有被加载了没被执行