Skip to content
New issue

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 #14

Open
chiwent opened this issue Jul 16, 2019 · 1 comment
Open

重学webpack #14

chiwent opened this issue Jul 16, 2019 · 1 comment
Labels

Comments

@chiwent
Copy link
Owner

chiwent commented Jul 16, 2019

重学webpack

在开始接触前端工程化的时候,第一个上手的工具就是webpack。最开始使用webpack,只是用在html+css+js的场景下进行打包配置。在这种场景下,项目工程的复杂度较低,基本上参照着webpack官网,配置好相关参数就可以跑通项目了。后来接触vue后,开始使用vue-cli脚手架简单搭建主体项目环境,也就只是稍微改动一些插件、loader以及其他配置。但是在接触到vue ssr后,逐渐意识到在webpack配置对前端开发的重要性,所以也就有了这篇笔记,重学webpack。

1 初始化项目

1.1 安装依赖

首先初始化项目:

npm init
npm install webpack webpack-cli webpack-dev-server --save-dev

在执行完npm init后,项目目录下会多出一个package.json的文件,在这里可以看得到项目下安装的webpack插件,也修改npm命令。

补充说明:

npm install安装相关工具时,如果使用了--save,依赖会添加到package.json中的dependencies选项中;如果使用了--save-dev,依赖会添加到package.json中的devDependencies中,注意区分。

在上述提到的插件中,webpack-dev-server可以提供一个简单的本地服务,并且具有热重载的功能。

1.2 编辑配置

在项目路径下,新建一个名为webpack.config.js的配置文件,基本结构如下:

module.exports = {
    mode: 'development',
    entry: ['./src/index.js'],
    output: {
        path: path.join(__dirname, 'dist'),
        filename: 'bundle.js' //如果担心缓存影响热更新效果,可以使用[name].[hash:8].js
    },
    externals: {},
    devtool: 'eval',
    resolve: {
        extensions: ['.js', '.vue', '.json'],
        alias: {
            'vue$': 'vue/dist/vue.esm.js',
            '@': resolve('src'),
        }
    },
    module: {
        rules: []
    },
    plugins: [],
    optimization: {},
    devServer: {}
}

简要说明一下该配置文件的内容:

  • mode:开发环境
  • devtool:顾名思义,就是方便我们开发调试的配置选项,我们可以在此配置source-map,打包出来的js文件代码映射到代码文件的具体位置,方便我们更快定位代码异常位置。

其中,开发环境推荐使用cheap-module-eval-source-map,生产环境推荐:
cheap-module-source-map(这也是下版本 webpack 使用-d命令启动debug模式时的默认选项)

其他的具体配置参数可以参考:webpack官方文档 - devtool以及深入浅出的webpack构建工具---devTool中SourceMap模式详解(四)

  • entry:入口文件
  • output:打包生成文件,按上述配置,就是将./src/index.js打包为./dist/bundle.js。其中,这里有几个关键的path变量信息,可参考:webpack中的path、publicPath和contentBase
  • externals:提供了“从输出的bundle中排除依赖的方法”,比如项目中通过CDN引入了jQuery这样的类库,然后我们又不需要将其打包(如果是通过CDN引入的工具库,都通过这种方式配置),就可以有以下配置:
external: {
  jquery: 'jQuery'
}

具体可参考:外部扩展(externals)

另外补充一点,如果需要全局使用jQuery,那么可以在webpack公共配置文件(webpack.base.conf.js)中设置,其中ProvidePlugin提供和暴露全局变量:

module.exports = {
  plugins: [
    new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery'
    })
  ]
}

假如我们要单独打包类似jQuery这样的类库,可以在公共配置中加上设置如下:

module.exports = {
  plugins: [],
  optmization: {
    cacheGroup: {
      commons: {
        test: /jquery/,
        name: 'jquery',
        chunks: 'all'
      }
    }
  }
}
  • resolve:指定模块如何被解析
  • module:模块,每个模块对应一个文件,webpack会从配置的entry开始递归查找出所有依赖的模块
  • plugins:扩展插件,webpack在构建的过程中会在特定时机广播对应事件,插件可以监听这些事件,在特定时机做对应的事情
  • optimization:优化策略,详见 https://webpack.docschina.org/configuration/optimization/
  • devServer:webpack-dev-server配置,比如代理之类的,具体可以查看vue-cli提供的模板

另外的一些概念:

  • loader:模块转换器,将原内容转换为新内容
  • chunk:代码块,一个chunk由多个模块组合形成,可用于代码分割以及合并

关于webpack配置中的process.env

在node当中,process表示的是当前node的进程,process.env就包括了系统环境信息,但是process.env不包括NODE_ENV这个属性,她是用户自定义的变量,用来判断当前环境。

而我们在package.json配置中就有关于环境变量NODE_ENV的配置(当然你放在其他文件下也没问题),如:

{
  'script': {
    'dev': "NODE_ENV=development webpack --config webpack.dev.conf.js"
  }
}

这样,我们就将NODE_ENV绑定到了process.env上,并且按以上配置,我们只能在webpack.dev.conf.js中以及它所引入的脚本中访问到process.env.NODE_ENV,其他地方都不可访问到。

2 构建流程

webpack的构建流程是串行的,如下:

  • 1.初始化参数:在package.json读取对应的命令配置,得出最终参数
  • 2.开始编译前的准备:实例化Compiler,加载所有plugin,执行对象的run方法并开始编译。接着根据配置中的entry找到所有入口文件
  • 3.编译模块:从入口文件出发,调用所有loader对模块进行编译,再找到模块依赖,重复上述步骤知道所有入口文件都经过处理。在loader处理完所有模块后,得到的每个模块以及它们之间的依赖关系图
  • 4.导出:根据入口和模块之间的依赖图,将代码组成一个个包含多个模块的chunk文件加入到输出列表,然后根据配置确定的输出路径和文件名,将chunk导出到项目目录中

流程图可以参考这里:https://juejin.im/post/5c6b78cdf265da2da15db125

webpack打包的规则是,一个入口文件对应一个bundle,该bundle包括了入口文件模块和其他依赖模块,按需加载的模块或者需要单独加载的模块则是分开打包生成其他的bundle。在这些bundle中,有一个较为特殊,就是manifest.bundle.js,被称作webpackBootstrap,它是最先加载的,负责解析webpack生成的其他bundle。

3.具体配置

3.1 loader

3.1.1 babel

安装:

npm i babel-loader babel-core babel-preset-env babel/plugin-transform-runtime --save-dev

babel是JavaScript的转码器,可以现代js脚本内容转换为ES5、ES3。下面介绍几个常用的babel loader:

  • babel-loader:不解释,必需
  • babel/core:核心包,不解释,必需
  • babel/preset-env:包括了所有ECMA标准
  • babel/preset-es2015:按照ES6标准编译,如果需要更高版本,可以使用如babeil-preset-es2017(ES8),如果需要覆盖最广的,就使用前面提到的babel-preset-env,等价于babel-preset-latest,包括了所有的bebal-preset-esxxxx
  • babel-stage-x:其概念类似于前面的preset-esxxxx,所包含的功能和支持的功能插件各有不同,其中,stage-0的功能范围最大,包括了stage-1、stage-2和stage-3的全部功能
  • babel-polyfill:polyfill,不解释
  • babel/plugin-transform-runtime:按需转换js,可以避免如babel-polyfill出现的全局转换,避免代码污染,也减小了打包体积

补充说明babel-stage-x对应的ECMA标准:

  • stage-0:strawman,任何TC39成员都可以提出的草案,随时被废弃。
  • stage-1:proposal,这是比较正式的提议,表示要进一步讨论。
  • stage-2:draft,在上一步基础上进行尽可能详细的讨论,到了这个阶段后,只允许增量修改。
  • stage-3:candidate,对提案的讨论基本完成,等待用户的反馈,只有发生重大问题时才会修改。
  • stage-4:经过了充分测试,已经准备写进新标准。
    stage标准是向下兼容的,比如stage0包括了stage1、stage2、stage3、stage4的内容。

    可以参考:What's the difference between babel-preset-stage-0, babel-preset-stage-1 etc?

在webpack配置对应的babel loader时,也需要在项目下创建.babelrc文件,下面举出demo:

// vue-cli的配置:
{
    "presets": [
        "es2015",
        "stage-3"
    ],
    "plugins": [
        "syntax-dynamic-import"
    ]
}

// react配置可以参考:
{
  "presets": ["es2015", "stage-2", "react"],
  "plugins": [
    "react-hot-loader/babel",
    "transform-function-bind",
    "transform-class-properties",
    "transform-export-extensions",
    ],
    "env": {
      "backend": {
        "plugins": [
          [ "webpack-loaders",
            { "config": "./webpack.config.babel.js"
            , "verbose": true
            }
          ]
        ]
      }
    }
  }
}

webpack的配置举例如下:

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        user: {
          loader: 'babel-loader?cacheDirectory=true', // cacheDirectory用于缓存babel的编译结果,加快重新编译的速度
          options: {
            presets: ['@babel/preset-env'],
            plugins: [
              '@babel/plughin-transform-runtime',
              '@babel/plugin-transform-modules-commonjs'
            ]
          }
        }
      }
    ]
  }
}

3.1.2 样式相关的loader

常见的css预处理器有less、sass、stylus等,它们都有对应的loader,以less和css的配置为例:

rules: [
    {
        test: /\.less$/,
        exclude: /node_modules/,
        use: ['style-loader',
        {
            loader: 'css-loader',
            options: {
                importLoaders: 2
            }
        }, 'less-loader', 'postcss-loader']
    },
    {
        test: /\.scss$/,
        use: [
            "style-loader", // 将 JS 字符串生成为 style 节点
            "css-loader", // �将 CSS 转化成 CommonJS 模块
            "sass-loader" // 将 Sass 编译成 CSS,默认使用 Node Sass
        ]
    },
    {
        test: /\.css$/,
        use: ['style-loader', 'css-loader', 'postcss-loader']
    }
]

// postcss.config.js
module.exports = {
  plugins: [
    require('autoprefixer')
  ]
}

3.1.3 静态资源相关loader

如果我们要处理一些图片,就很大概率上会接触到file-loader和url-loader,二者有相似之处,二者在处理图片时可以将其打包到dist目录,接着会获取图片模块的地址,并将地址返回到引入模块的变量中。url-loader基本可以实现file-loader的功能,但是在url-loader是将图片转换为base64直接放入bundle.js下,而file-loader会将图片放到dist目录下。

rules: [
  {
    test: /\.(png|jpg|gif|jpeg)$/,
    use: {
        loader: 'url-loader',
        options: {
            name: '[name]_[hash].[ext]', // placeholder 占位符
            outputPath: 'images/', // 打包文件名
            limit: 204800, // 小于200kb则打包到js文件里,大于则使用file-loader的打包方式打包到imgages里
        },
    },
  },
  {
    test: /\.(eot|woff2?|ttf|svg)$/,
    use: {
        loader: 'url-loader',
        options: {
            name: '[name]-[hash:5].min.[ext]', // 和上面同理
            outputPath: 'fonts/',
            limit: 5000,
        }
    },
  }
]

3.2 plugin

3.2.1 常用插件

  • webpack-merge:提取公共配置,分离生产环境和开发环境的配置文件。比如在vue-cli中会生成webpack.base.conf.js(公共配置)、webpack.dev.conf.js(开发环境配置)、webpack.prod.conf.js(生产环境配置)的配置文件。然后再合并webpack配置项,比如合并webpack.base.conf.jswebpack.dev.conf.js
  • clean-webpack-plugin:每次编译前自动清空dist目录
  • html-webpack-plugin:从html模板自动生成最终的html
  • defineplugin:声明process.env.NODE_ENV的值
  • extract-text-webpack-plugin:提取css到文件中
  • optimize-css-assets-webpack-plugin:合并相同的css样式文件
  • css-split-webpack-plugin:拆分过大的csss文件
  • mini-css-extract-plugin:分开打包css文件
  • webpack-md5-hash:webpack自身生成文件hash值的方法是不确定的,为了确保hash值是根据文件内容生成的,可以使用该插件
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = reuqire('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
  // 多入口文件打包
    entry: {
        index: './src/js/index.js',
        login: './src/js/login.js'
    },
    optimization: {
        minimizer: [new OptimizeCSSAssetsPlugin({})]
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin(), // 开启全局的模块热替换(HMR)
        new webpack.NamedModulesPlugin(), // 当模块热替换(HMR)时在浏览器控制台输出对用户更友好的模块名字信息
        new webpack.DefinePlugin({
            'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
        }),
        new CleanWebpackPlugin(),
        // 以下两个热重载插件
        new webpack.NamedModulesPlugin(),  
        new webpack.HotModuleReplacementPlugin(), 
        new ExtractTextPlugin('css/[name].[hash].css'),
        // 多页打包
        new HtmlWebpackPlugin({
            filename: 'index.html',
            template: './src/index.html',
            chunks: ['index']
        }),
        new HtmlWebpackPlugin({
            filename: 'login.html',
            template: './src/login.html',
            chunks: ['login']
        }),
        new MiniCssExtractPlugin({
            // Options similar to the same options in webpackOptions.output
            // both options are optional
            filename: devMode ? '[name].css' : '[name].[hash].css',
            chunkFilename: devMode ? '[id].css' : '[id].[hash].css',
        }),
        new CSSSplitWebpackPlugin({
            size: 4000,
            filename: '[name]-[part].[ext]'
        })
    ],
    module: {
        noParse:/jquery|lodash/, // 对于没有使用require和import,而是通过CDN引用的模块,不需要使用webpack进行解析依赖
        rules: [
            {
                test: /\.(sa|sc|c)ss$/,
                use: [
                devMode ? 'style-loader' : MiniCssExtractPlugin.loader,
                'css-loader',
                'postcss-loader',
                'sass-loader',
                ],
            }
        ]
    },
    output: {
        filename: 'js/[name].js',
        path: path.resolve(__dirname, 'dist')
    }
}

4 优化

4.1 巧用resolve

可以通过resolve.modules:[path.resolve(__dirname, 'node_modules')]来定位第三方模块,默认是node_modules;设置resolve.alias来生成一个快速引用符号指向目标目录;在使用loader的时候,可以通过test、exclude、include、正则来缩小搜索范围

  • resolve.extensions:使用require或import引入文件时可以省略后缀:
  • resolve.alias:简化引用路径:
  • resolve.mainFields:通过第三方插件的package.json中main字段(大多数第三方模块都采用这个字段)中的地址来引用文件
  • resolve.modules:限制引入第三方包的范围
  • resolve.mainFiles:在一定范围下默认优先查找的文件名

另外,使用alias可以加快webpack查找模块的速度。

resolve: {
    extensions: ['.js', '.css', '.vue'],
    alias: {
        'vue$': 'vue/dist/vue.esm.js',
        '@': resolve('src'),
    },
    mainFields: ['style', 'main'],
    modules:[path.resolve(__dirname, node_modules), my_modules], //只能引入node_modules和mu_modules下的包
    mianFiles:['index.js','main.js']
}

4.2 抽离公共代码

如果在两个组件中都引用了共同的组件,那么我们可以将公共组件代码抽离出来

const path = require('path');
module.export = {
    entry: {
        index: './src/index',
        login: './src/login'
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    },
    optimization: {
        splitChunks: {
            cacheGroups: {
                common: {
                    chunks: 'initial',
                    minChunks: 2, // 使用两个同时引用的模块时才抽离出来
                    minSize: 0  // 限制大小,小于指定值就不抽离
                }
            }
        }
    }
}

最后打包的结果中会生成index.jslogin.js

或者是:

const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
plugins:[
    new CommonsChunkPlugin({
        chunks:['chunkA','chunkB'], //从哪些chunk中提取
        name:'common',  // 提取出的公共部分生成一个新的chunk,文件为common.js
    })
]

4.3 使用DllPlugin减少基础模块的编译次数

DllPlugin是动态链接库插件,原理是将网页依赖的基础模块抽离出来打包到dll文件中,当需要导入的模块存在于某个dll文件中,该模块不再被打包,而是通过dll获取。

// webpack.dll.config.js
const path = require('path');
const webpack = require('webpack');
module.exports ={
  entry: {
    vendor: ['react', 'redux', 'react-router'],
  },
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].dll.js',
    library: '[name]_[hash]'    //提供全局的变量
  },
  plugins: [
    new webpack.DllPlugin({
      path: path.join(__dirname, 'dist', '[name].manifest.json'),
      name: '[name]_[hash]',
    }),
  ],
};

// package.json
"scripts": {
    "dev": "webpack-dev-server",
    "build": "webpack",
    "dll":"webpack --config webpack.dll.config.js"
},

// webpack.base.conf.js
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname,'dist'),
    filename: 'index_bundle.js',
  },
  plugins: [
    new webpack.DllReferencePlugin({
      context: path.join(__dirname),
      manifest:path.resolve(__dirname,'dist','vendor.manifest.json')
    }),
    new HtmlWebpackPlugin(),
    new AddAssetHtmlPlugin({
      filepath: path.resolve(__dirname,'dist','vendor.manifest.json')
    }),
  ],
};

作者:梦想攻城狮
链接:https://juejin.im/post/5bd128b56fb9a05ce1729333
来源:掘金

打包后生成:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Webpack App</title>
  </head>
  <body>
    <script type="text/javascript" src="vendor-manifest.json"></script>
    <script type="text/javascript" src="index_bundle.js"></script>
  </body>
</html>

4.4 使用IgnorePlugin忽略某些模块打包

IgnorePlugin可以让webpack不打包指定的模块:

module.exports = {
    ...
    plugins: [
        new webpack.IgnorePlugin(/moment$/)
    ]
    ...
}

4.5 生产环境中将提前构建的包同步到dist

const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
    plugins: [
        new CopyWebpackPlugin([
            {
            from: path.resolve(__dirname, '../static'),
            to: config.build.assetsSubDirectory,
            ignore: ['.*']
            }
        ])
    ]
}

4.6 代码压缩

webpack内置的uglifyjs插件可以实现js代码压缩:

const UglifyJSPlugin = require('webpack/lib/optimize/UglifyJsPlugin');
//...
plugins: [
    new UglifyJSPlugin({
        compress: {
            warnings: false,  //删除无用代码时不输出警告
            drop_console: true,  //删除所有console语句,可以兼容IE
            collapse_vars: true,  //内嵌已定义但只使用一次的变量
            reduce_vars: true,  //提取使用多次但没定义的静态值到变量
        },
        output: {
            beautify: false, //最紧凑的输出,不保留空格和制表符
            comments: false, //删除所有注释
        }
    })
]

作者:superMaryyy
链接:https://juejin.im/post/5b652b036fb9a04fa01d616b
来源:掘金

另外,使用css-loader?minimize不仅可以删除样式文件的空格,还可以语义化地压缩css代码(rgb转色彩名)

4.7 使用静态资源CDN

参考:

const ExtractTextPlugin = require('extract-text-webpack-plugin');
const {WebPlugin} = require('web-webpack-plugin');
//...
output:{
 filename: '[name]_[chunkhash:8].js',
 path: path.resolve(__dirname, 'dist'),
 publicPatch: '//js.cdn.com/id/', //指定存放JS文件的CDN地址
},
module:{
 rules:[{
     test: /\.css/,
     use: ExtractTextPlugin.extract({
         use: ['css-loader?minimize'],
         publicPatch: '//img.cdn.com/id/', //指定css文件中导入的图片等资源存放的cdn地址
     }),
 },{
    test: /\.png/,
    use: ['file-loader?name=[name]_[hash:8].[ext]'], //为输出的PNG文件名加上Hash值 
 }]
},
plugins:[
  new WebPlugin({
     template: './template.html',
     filename: 'index.html',
     stylePublicPath: '//css.cdn.com/id/', //指定存放CSS文件的CDN地址
  }),
 new ExtractTextPlugin({
     filename:`[name]_[contenthash:8].css`, //为输出的CSS文件加上Hash
 })
]

作者:superMaryyy
链接:https://juejin.im/post/5b652b036fb9a04fa01d616b
来源:掘金

4.8 多进程打包

可以使用HappyPack这个插件进行webpack的多进程打包,以下放出网上找来的一段相关配置,来自:加速 Webpack,文章内也有具体的配置说明,详情可点击链接访问

const path = require('path');
    const  ExtractTextPlugin =  require('extract-text-webpack-plugin');
    const  HappyPack = require('happypack');
    module.exports = { 
        module: { 
            rules: [ 
                {    test: /\.js$/, 
                    // 把对 .js 文件的处理转交给 id 为 babel 的 HappyPack 实例 
                    use:['happypack/loader?id=babel'], 
                    // 排除 node_modules 目录下的文件,node_modules目录下的文件都是采用的 ES5 语法,没必要再通过 Babel 去转换 
                    exclude: path.resolve(__dirname, 'node_modules'),
                 }, 
                { 
                    // 把对 .css 文件的处理转交给 id 为 css 的 HappyPack 实例
                     test: /\.css$/, 
                     use:ExtractTextPlugin.extract({ 
                        use: ['happypack/loader?id=css'],
             }), 
        }, 
] },
    plugins: [ 
        new HappyPack({ 
            // 用唯一的标识符 id 来代表当前的HappyPack 是用来处理一类特定的文件 
        id: 'babel', 
            // 如何处理 .js 文件,用法和 Loader配置中一样 
        loaders: ['babel-loader?cacheDirectory'],
     }),
        new HappyPack({ 
                id: 'css', 
                    // 如何处理 .css 文件,用法和Loader 配置中一样 
                loaders: ['css-loader'], }), 
                new ExtractTextPlugin({ 
                    filename: `[name].css`, 
            }), 
        ],
    };

4.9 runtime和manifest

在webpack项目构建中,有三种基本的代码类型:

  • 1.自身编写的代码
  • 2.第三方依赖代码(vendor)
  • 3.webpack中的runtime和manifest,管理所有模块的交互

在单页应用中,客户端发起网络请求后,从服务端返回的文件主要是入口的html文件和一系列bundle文件,然后交由浏览器解析处理。经由webpack打包过后的项目,在不同的打包策略模式下,代码的执行逻辑是不同的,但是它们都需要通过webpack来进行管理模块间的交互。而runtime和manifest就是指在浏览器运行时,webpack通过连接模块化应用程序的所有代码。runtime包括了在模块交互时,连接模块所需的加载和解析逻辑,包括浏览器中已加载模块的连接、懒加载模块的执行逻辑。

为了更加清晰地理解manifest,我们简要复述一遍webpack的工作流程:

  • 从入口文件开始递归,生成依赖关系图
  • 将所有文件转换为模块
  • 根据依赖关系图,按照配置文件将模块分组打包生成若干bundle
  • 通过script标签将打包的bundle引入html,通过manifest管理bundle文件的运行和加载
当编译器(compiler)开始执行、解析和映射应用程序时,它会保留所有模块的详细要点。这个数据集合称为 "Manifest",当完成打包并发送到浏览器时,会在运行时通过 Manifest 来解析和加载模块。无论你选择哪种模块语法,那些 import 或 require 语句现在都已经转换为 __webpack_require__ 方法,此方法指向模块标识符(module identifier)。通过使用 manifest 中的数据,runtime 将能够查询模块标识符,检索出背后对应的模块。

-- webpack官方文档

当webpack写入bundle时,它会维护一个manifest,你可以在项目中生成的bundle找到它,它内部描述了webpack应该加载的文件。如果文件的hash值改变,manifest也会改变,然后也跟着一些分离出来的代码共同打包。这显然不是我们想要的,所以我们需要提取出manifest:

new webpack.optimize.CommonsChunkPlugin({
    name: 'manifest' // 用于提取manifest
})

4.10 缓存优化

浏览器加载js文件时,如果文件名和浏览器本地缓存一致,那么就不会向后台发送请求。假如我们的业务代码中做了修改,但是webpack打包出来的文件名对应的hash值不变,那么就不会用上新代码。另外,在前端项目中一般是只有业务代码才会频繁变化,而第三方依赖是很少变化的,所以我们希望浏览器不用再重复地向服务端发送请求,这就要求我们进行代码分块打包,将一些第三方依赖集中整合进单个bundle下。所以,我们可以借助webpack.optimize.CommonsChunkPlugin将manifest、第三方依赖、业务代码分别独立打包

4.11 打包分析

可以使用webpack-bundle-analyzer对webpack打包结果进行分析,支持可视化的。

module.exports = {
  entry: './index.js',
  output: {
    filename: 'bundle[chunkhash:8].js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks: function (module, count) {
        // 将node_modules的依赖集中打包,注意主次关系,依赖要放在manifest前面
        return (
          module.resource &&
          /\.js$/.test(module.resource) &&
          module.resource.indexOf(
            path.join(__dirname, '../node_modules')
          ) === 0
        )
      }
    }),
    // 抽出mainfest
    new webpack.optimize.CommonsChunkPlugin({
          name: 'manifest',
          chunks: ['vendor']
    })
  ]
}




参考:





扩展阅读:

@chiwent
Copy link
Owner Author

chiwent commented Feb 26, 2020

关于在工程中混用ES6模块 和 CommonJS规范

在工程中如果代码混用了这两种规范,可能就遇到构建失败的情况。实际上,这样的方式也是不提倡的。可以参考下面的讨论:

webpack/webpack#4039 (comment)

事实上,webpack内部就维护了包管理工具,支持ES Module、Commonjs、AMD 模块化规范。

如果需要处理这样的冲突问题,可以对babel的配置文件进行修改,将modules的配置选项设置为commonjs(js最终编译得到CommonJS规范)或者umd(js最终编译得到umd规范)以支持这两种模块规范的混用。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant