深入浅出webpack-打包优化

本文承接上一篇 深入浅出webpack -- loader和plugin原理及区别

主要从下面几个点入手优化:

项目本身:

  • 减少依赖嵌套深度
  • 使用尽可能少的处理

webpack层面:

  • Dll处理
  • 通过include减少loader范围
  • HappyPack
  • Uglify优化
  • 减少resolve,sourcemap,cache-loader用新版本的node和webpack

代码分割

一般vue打包后的文件中会有三个常见的js文件,app.js业务代码 vendor.js第三方代码,manifest.js,webpack的代码,拆分的规范一般是:

  • 多页面: 主业务代码 + 公共依赖 + 第三方包 + webpack运行代码
  • 单页面:主业务代码 + 异步模块 + 第三方包 + webpack运行代码

1 单页面

对于单页面,主要是拆分,减少体积,把需要异步加载的改成异步加载,主要就是做一个异步的拆分。

在webpack3中 需要用下面的插件来进行代码分割

new webpack.optimize.CommonsChunksPlugin({
     name:'vendor',
     minChunks:'infinity'
   }),
   new webpack.optimize.CommonsChunksPlugin({
     name:'manifest',
     minChunks:'infinity'
   }),
   new webpack.optimize.CommonsChunksPlugin({
     name:'app.js',
     minChunks:2
   }),

webpack4 配置一个属性

optimization:{
    minimize:true, // 压缩代码,减少体积
    splitChunks:{
      name:true,
      chunks:"all", // 'initial' 只对入口文件进行公共模块分析, 'all' 对所有文件进行模块分析 'async'
      minSize: 30000, // 默认30000, 大于30kb的文件进行提取
      cacheGroups:{
        mode1:{  // 自定义要提取的模块
          test:/mode1/,
        },
        vendor: { // 提取node_modules中文件 都喜欢把第三方依赖都打包在一起
          test: /([\\/]node_modules[\\/])/,
          name: "vendor"
        }
      }
    },
    runtimeChunk:true // 把webpack运行代码的提取出来
  },

运行webpack 

打包后的文件也都被引入到了index.html中。

2 splitChunks基本配置

(1)chunks:分割代码的模式

  • async 异步代码分割模式:只分割异步引入的代码

异步代码指的是异步引入的代码模块单独打包成一个或多个文件,下面是异步引入的例子:

//异步加载模块
function getComponent () {
 return import(/* webpackChunkName:"lodash" */ 'lodash').then(({ default: _ }) => {
   var element = document.createElement('div')
   element.innerHTML = _.join(['Dell', ' ', 'Lee', '-'])
   return element
 })
}
getComponent().then(el => {
 document.body.appendChild(el)
})
  • initial 同步代码分割模式:只分割同步引入的模块
    同步代码引入方式:
//同步加载模块
import _ from 'lodash'  //第三方库
import test from './test.js' //业务代码
import jquery from 'jquery'  //第三方库
console.log(test.name)

var element = document.createElement('div')
element.innerHTML = _.join(['Dell', ' ', 'Lee', '-'])
document.body.appendChild(element)
console.log(jquery('div'))
  • all 同步异步都分割模式:在所有配置条件都满足的情况下,无论如何引入模块都会进行分割

(2)minSize和maxSize(单位:字节)

   minSize指的是引入的模块的最小值
   maxSize指的是引入的模块的最大值,当引入的模块大小大于最大值时,weback会尝试将这个模块以最大值为准分割成多个模块,前提是这个模块可以分割,比如lodash的提交大于50KB,那么设置maxSize:5000时,依然打包出一个文件来,故此属性一般不用

(3)minChunks:模块至少使用次数

   当值为2时,代表只引用了一次的模块不做分割打包处理

(4)maxAsyncRequests:同时加载的模块数量最大值

当需要分割的模块同步引入个数超出限时时,webpack只会分割限制值内的模块,其它的将不做处理

(5)maxInitialRequests:

   首次加载引入模块可分割的最大值

(6)automaticNameDelimiter:

    缓存组名称和生成文件名称之间的连接字符串

(7)name

    设置为true时,缓存组里面的filename生效,覆盖默认命名方式

const { CleanWebpackPlugin } = require('clean-webpack-plugin');
   new CleanWebpackPlugin(),

清除之前的dist

webpack3的压缩

    new webpack.optimize.UglifyJsPlugin(),

2 多页面应用

对于多页面,我们要做的就是提取公共依赖,把几个页面中都用到的依赖打包到一个文件中。

有时候我们并不希望业务代码中混入了第三方代码,或者webpack 的代码。就需要把这些不希望出现在业务中的代码拆分成单独的文件。

多个html文件,多个入口

var extractTextCss=require('extract-text-webpack-plugin');
var htmlWebpackPlugin=require('html-webpack-plugin');
module.exports= {
  mode:'production',
	entry:{
   app:"./src/app.js",
   app2: "./src/app2.js"
	},
	output:{
		path:__dirname+"/dist",
		filename:"./[name].bundle.js",
	},
	module:{
		rules: [    
     {
       test:/\.css$/,
       use:extractTextCss.extract({
        fallback:{
           loader:'style-loader',
           options:{
            //insertInto:"#mydiv",
            //transform:"./transform.js"
           }
         },
        use:[
         {
           loader:'css-loader',
           options:{
             /*modules:{
              localIdentName:'[path][name]_[local]_[hash:4]'
             }   */                 
           } 
         },
        ]
       })
     },
     {
      test:/\.(png|jpg|jgeg|gif)$/,
      use:[
        {
          loader:'url-loader',
          options:{
            //默认是[hash].[ext]
            name:'[name].[hash:4].[ext]',
            outputPath:"assets/img",
            publicPath:"assets/img",
            limit:5000
          }
        },
        {
          loader:'img-loader',
          options:{
            plugins:[
              require('imagemin-pngquant')({
                speed:2 //1-11
              }),
              require('imagemin-mozjpeg')({
                quality:80 //1-100
              }),
              require('imagemin-gifsicle')({
                optimizationLevel:1 //1,2,3
              })
            ]
          }
        },
      ]
     },
    {
      test:/\.html$/,
      use:{
        loader:'html-loader',
        options:{
          attrs:["img:data-src"]
        }
      }
    } 
		]
	},
  plugins:[
   new extractTextCss({
    filename:'[name].min.css'
   }),
   new htmlWebpackPlugin({
   	filename:"index.html",
     template:"./src/index.html",
     chunks:['app']
   }),
   new htmlWebpackPlugin({
    filename:"index1.html",
    template:"./src/index1.html",
    chunks:['app2']
  }),
  ]
}

index.html 和 index1.html内容一样

<!DOCTYPE html>
<html>
<head>
	<title></title>
 
</head>
<body>
 <div id="mydiv" class='div1'></div>
 <div  class='div1 img1'></div>
 <div  class='div1 img2'></div>
 <div  class='div1 img3'></div>
 <div  class='div1 img4'></div>
 
</body>

 项目目录结构

打包后 

配置代码拆分:(下面代码中的Setting可以理解为一个存储当前常量,环境信息的对象)

 // 代码拆分
  optimization: {
    minimize: Setting.NODE_ENV === 'production', // This is true by default in production mode.
    runtimeChunk: {
      name: 'manifest',
    }, // 把webpack运行代码的提取出来
    splitChunks: {
      chunks: 'initial',
      minChunks: 1,
      minSize: 30000,
      maxAsyncRequests: 5,
      maxInitialRequests: 3,
      name: false,
      cacheGroups: {
        commons: {
          test: /common/, // 把公共代码提取出来
          name: Setting.common,
        },
        vendor: { // 提取node_modules中文件 都喜欢把第三方依赖都打包在一起
          test: /([\\/]node_modules[\\/])/,
          name: 'vendor',
        },
      },
    },
  },

 打包后:

看index.html知道 只引入了app2.js文件。

还需要进行下面的配置,才可以把所有文件都引入:

 config.plugins.push(new Html({
    inject: true,
    title,
    env: Setting.NODE_ENV,
    template: 'public/index.html',
    filename: `${k}.html`,
    chunks: [k, Setting.common, 'vendor', 'manifest'],
    minify: Setting.NODE_ENV === 'production' || Setting.NODE_ENV === 'analyze' ? {
      removeComments: true,
    } : false,
  }));

 

  两个html文件中都引入了webapck代码,和第三方依赖

分析代码

下面两种方式可以获得可视化的打包结果分析

1 官方版本

(1)Mac webpack --profile --json > stats.json

(2)Window: webpack --profile --json | Out-file 'stats.json' -Encoding OEM

然后将输出的json文件上传到如下网站进行分析

http://webpack.github.io/analyse/

选择stats.json文件可以看到详细的信息,生成了可视化的页面,然后打开 Modules页面,可以分析每一个文件的打包时间和大小

 2 社区版本

npm  i  webpack-bundle-analyzer  --save  

webpack引入插件 

 const wba = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
    
 new wba()

可以用这样的方式分析每个模块的大小

介绍一种提取css文件的方式, 比较支持webpack4 

npm install mini-css-extract-plugin
const miniCssExtractPlugin = require('mini-css-extract-plugin');
 new extractTextCss({ // 不支持hash命名

    filename:'[name].min.css'

   }),

 new miniCssExtractPlugin({ // 支持hash
      filename: '[name].[hash].css'
    }),

style-loader换成 miniCssExtractPlugin.loader

可以把css文件也加上hash。

dll处理

  • 将网页依赖的基础模块抽离出来,打包到一个个单独的动态链接库中。在一个动态链接库中可以包含多个模块。
  • 当需要导入的模块存在于某个动态链接库中时,这个模块不能被 再次打包,而是去动态链接库中获取。
  •  页面依赖的所有动态链接库都需要被加载。

为什么为We b项目构建接入动态链接库的思想后,会大大提升构建 速度呢?原因在于,包含大量复用模块的动态链接库只需被编译一次, 在之后的构建过程中被动态链接库包含的模块将不会重新编译,而是直 接使用动态链接库中的代码。由于动态链接库中大多数包含的是常用的 第三方模块,例如 react、react-dom,所以只要不升级这些模块的版 本,动态链接库就不用重新编译。

Webpack已经内置了对动态链接库的支持,需要通过以下两个内置的插件接入。

  • DllPlugin插件:用于打包出一个个单独的动态链接库文件。
  • DllReferencePlugin插件:用于在主要的配置文件中引入DllPlugin 插件打包好的动态链接库文件。

webpack.dll.js

const webpack=require('webpack');
module.exports={
  entry:{
  	jquery:["jquery"],
  	loadsh:["loadsh"]
  },
  output:{
    path:__dirname+"/src/dll",
    filename:"./[name].js",
    //引用名
    library:'[name]'
  },
  plugins:[
     new webpack.DllPlugin({
      path:__dirname+"/src/dll/[name].json",
      name:"[name]"
     })
  ]  
}

注意这里的output.library属性要与DllPlugin中的name属性保持一致。dll打包后的文件需要单独引入html,因为不会再被打包进主文件内,也相当于做了代码拆分。

webpack --config webpack.dll.js现把第三方依赖打包好,src下面多了一个dll文件夹,这就是构建出的动态链接库文件。

如果需要压缩 可以增加配置,例子:

const webpack = require('webpack');
const TerserPlugin = require('terser-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  mode: "development",
  entry:{
    vue: ["vue"],
  },
  output: {
    path: __dirname + "/src/dll/",
    filename: '[name].dll.js',
    library: 'dll_[name]'
  },
  optimization: {
    minimize: true,
    // 压缩js
    minimizer: [
			new TerserPlugin({
        parallel: true, // 启动多进程压缩 官方建议
      })
		]
  },
  
  plugins:[
    new CleanWebpackPlugin(), // 清空得是output中得path
    new webpack.DllPlugin({
      path: __dirname+'/src/dll/[name].dll.json',
      name: 'dll_[name]',
    })
  ]
}

entry: ['vue-router', 'vue/dist/vue.esm.js'] vue项目中处理

json是给到webpack的

webpack.config.js 增加两个插件,这样每次打包的时候jquery和loadsh就不用再打包了,直接用打包好的。大大提升了打包速度。

new webpack.DllReferencePlugin({
     manifest:require('./src/dll/jquery.json')
   }),
   new webpack.DllReferencePlugin({
     manifest:require('./src/dll/loadsh.json')
   })

并且用下面的插件,把dll/*.js文件插入html。

 // 将某个文件打包输出去,并在html中自动引入该资源
          new AddAssetHtmlPlugin([{
            outputPath: "js/",
            filepath: path.resolve(__dirname,'src/dll/vue.dll.js') // 文件路径
          }])

happypack

由于有大量文件需要解析和处理,所以构建是文件读写和计算密集 型的操作,特别是当文件数量变多后,Webpack构建慢的问题会显得更 为严重。运行在Node.js之上的Webpack是单线程模型的,也就是说 Webpack需要一个一个地处理任务,不能同时处理多个任务。

文件读写和计算操作是无法避免的,那能不能让 Webpack 在同一 时刻处理多个任务,发挥多核CPU电脑的功能,以提升构建速度呢?

HappyPack就能让Webpack 做到这一点,它将任务分解给多个子进程去并发执行,子进程处理完后 再将结果发送给主进程。

由于 JavaScript 是单线程模型,所以要想发挥多核 CPU 的功能,就 只能通过多进程实现,而无法通过多线程实现。

//建议当文件较多的时候再使用这个,如果只有一两个反而会拖慢。
const HappyPack=require('happypack');
const os=require('os'); // 拿到操作系统
const happyThreadPool=HappyPack.ThreadPool({size:os.cpus().length}) // 新建进程池
{
        test: /\.js$/,
        loader: 'happypack/loader?id=happyBabel',  // 对js的处理使用happypack
        include: [resolve('src')]
      },
new HappyPack({
      id:'happyBabel',
      loaders:[
        {
          loader:'babel-loader?cacheDirectory=true' // 代替babel-loader
        }
      ],
      threadPool:happyThreadPool,
      verbose:true
    }),

长缓存

主要原理是充分利用浏览器的缓存机制,提高首页渲染速度。

  • 把hash改为chunkhash
  • NamedChunksPlugin和NameModulesPlugin
  • mini-css-extract-plugin

使用hash 

 output:{
    path:__dirname+"/dist",
    filename:"./[name].[hash].js",
  },

 

 每一个文件的hash都是一样的。

改变一下app.js文件,把ma和mb文件换一个循序

 import lod from "loadsh";
 //import './css/test1.css';
  import mb from "./moduleb.js";
 import ma from "./modulea.js";

 a(988989893232);
  
 /*require.ensure(["./moduleb"],function(){
    var ma=require('./modulea.js');
 })*/
 console.log(22);

webpack  打包

只改变了一个文件,导致多个文件的hash都发生了改变。

把hash换成chunkhash 

output:{
    path:__dirname+"/dist",
    filename:"./[name].[chunkhash].js",
  },

webapck 打包发现每一个文件的hash 都不一样。

 import lod from "loadsh";
 //import './css/test1.css';
 import ma from "./modulea.js";
 import mb from "./moduleb.js";

 a(988989893232);
  
 /*require.ensure(["./moduleb"],function(){
    var ma=require('./modulea.js');
 })*/
 console.log(22);

再把循序换一下,webpack打包,发现还是所有文件的hash都改变了

 

 增加两个插件

new webpack.NamedChunksPlugin(),

new webpack.NamedModulesPlugin(),

打包发现 chunks不一样了 

 

再改变一下app.js模块引入循序,打包发现只有改变的文件的hash改变了,其它的还保持原来的hash,这样上线发布以后,那些没有改变的文件就可以继续使用浏览器的缓存。

缩小文件搜索范围

1 resolve.modules配置优化

resolve.modules的默认值是['node_modules'],含义是先去当前目录 的./node_modules 目录下去找我们想找的模块,如果没找到,就去上一 级目录../node_modules中找,再没有就去../../node_modules中找,以此类 推,这和Node.js的模块寻找机制很相似。

当安装的第三方模块都放在项目根目录的./node_modules 目录下 时,就没有必要按照默认的方式去一层层地寻找,可以指明存放第三方 模块的绝对路径,以减少寻找,配置如下:

  resolve: {
    ...
    modules: [path.resolve(__dirname, 'node_modules')],
  },

2 resolve.alias的配置优化

在实战项目中经常会依赖一些庞大的第三方模块,以 React库为 例,安装到 node_modules目录下的React库的目录结构如下:

可以看到在发布出去的React库中包含两套代码。

  • · 一套是采用 CommonJS 规范的模块化代码,这些文件都放在 lib目录下,以package.json中指定的入口文件react.js为模块的入口。
  • · 一套是将React的所有相关代码打包好的完整代码放到一个单独的 文件中,这些代码没有采用模块化,可以直接执行。其中dist/react.js 用 于开发环境,里面包含检查和警告的代码。dist/react.min.js用于线上环 境,被最小化了。

在默认情况下,Webpack会从入口文件./node_modules/react/react.js 开始递归解析和处理依赖的几十个文件,这会是一个很耗时的操作。通 过配置resolve.alias,可以让Webpack在处理React库时,直接使用单独、 完整的react.min.js文件,从而跳过耗时的递归解析操作。

resolve: {
    extensions: ['.js', '.jsx'],
    alias: {
      '@': path.resolve(__dirname, 'src'),
      react: path.resolve(__dirname, './node_modules/react/dist/react.min.js'),
    },
    modules: [path.resolve(__dirname, 'node_modules')],
  },

此处具体项目还需要看具体路径,引入的需要是react生产环境的压缩代码。 

除了 React库,大多数库被发布到 Npm仓库中时都会包含打包好的 完整文件,对于这些库,也可以对它们配置alias。

这里注意,要这样引入:

      react: isDev ? path.resolve(__dirname, './node_modules/react/cjs/react.development.js') : path.resolve(__dirname, './node_modules/react/cjs/react.production.min.js'),

开发环境引入开发环境的react,不然页面会不显示。

alias也可以解决重复打包的问题,比如vue.esm.js被打包了两次:

一次:node_modules/vue/dist/vue.esm.js

还有一次:node_modules/am4-pub-js-common-component/vue/dist/vue.esm.js

配置resolve.alias:

    alias: {
      'vue': path.resolve(process.cwd(), 'node_modules', 'vue'),
    },

 之后再打包,可以看到只剩下一个vue了:

但是,对某些库使用本优化方法后,会影响到后面要讲的使用 Tree-Sharking 去除无效代码的优化,因为打包好的完整文件中有部分代 码在我们的项目中可能永远用不上。一般对整体性比较强的库采用本方 法优化,因为完整文件中的代码是一个整体,每一行都是不可或缺的。 但是对于一些工具类的库如 lodash,我们的项目中可能只用到 了其中几个工具函数,就不能使用本方法去优化了,因为这会导致在我们的输出代码中包含很多永远不会被执行的代码。

 Tree-Sharking是一种按需进入的技术,只会引入我们用到的函数。

3 resolve.extensions的配置优化

 resolve: {
    extensions: ['.js', '.json'],
}

当遇到 require('./data')这样的导入语句时,Webpack 会先去寻找./data.js文件,如果该文件不存在,就去寻找./data.json文 件,如果还是找不到就报错。

如果这个列表越长,或者正确的后缀越往后,就会造成尝试的次数 越多,所以resolve.extensions 的配置也会影响到构建的性能。在配置 resolve.extensions时需要遵守以下几点,以做到尽可能地优化构建性 能。

  • · 后缀尝试列表要尽可能小,不要将项目中不可能存在的情况写到 后缀尝试列表中。
  • · 频率出现最高的文件后缀要优先放在最前面,以做到尽快退出寻 找过程。
  • · 在源码中写导入语句时,要尽可能带上后缀,从而可以避免寻找 过程。例如在确定的情况下将require('./data')写成 require('./data.json')。

4 module.noParse的配置优化

module.noParse配置项可以让Webpack忽略对部 分没采用模块化的文件的递归解析处理,这样做的好处是能提高构建性 能。原因是一些库如 jQuery、ChartJS庞大又没有采用模块化标准,让 Webpack解析这些文件既耗时又没有意义。

在前面讲解优化 resolve.alias 配置时讲到,单独、完整的 react.min.js 文件没有采用模块化,让我们通过配置module.noParse忽略 对react.min.js文件的递归解析处理,相关的Webpack配置如下:

注意,被忽略掉的文件里不应该包含import、require、define等模块 化语句,不然会导致在构建出的代码中包含无法在浏览器环境下执行的 模块化语句。

以上就是所有和缩小文件搜索范围相关的构建性能优化方面的内容 了,在根据自己项目的需要按照以上方法改造后,构建速度一定会有所 提升。

 module: {
    noParse: [/react\.min\.js$/],
    ...
}

开发实践:

moment.js

一般情况下:

 只有main和jsnext:main,而./moment.js是编译好的,./dist/moment.js是module模式,webpack不识别jsnext:main配置,不会去加载模块化的moment.js,所以没有必要再对moemnt.js进行解析,

  • main : 定义了 npm 包的入口文件,browser 环境和 node 环境均可使用, 其中 .js 文件是使用 commonJS 规范的语法(require('xxx')),
  • jsnext:main: 是用 ESM 规范的语法(import 'xxx')
  • module: .mjs 是用 ESM 规范的语法(import 'xxx')
"main": "lib/index.js",  // main 
  "module": "lib/index.mjs", // module
 module: {
    noParse: function(content) {
        return /moment/i.test(content)
    }
    ...
}

多进程压缩代码

 当Webpack有多个JavaScript文件需要输出和 压缩时,原本会使用UglifyJS去一个一个压缩再输出,但是 ParallelUglifyPlugin会开启多个子进程,将对多个文件的压缩工作分配 给多个子进程去完成,每个子进程其实还是通过 UglifyJS 去压缩代码, 但是变成了并行执行。所以ParallelUglifyPlugin能更快地完成对多个文 件的压缩工作。

cnpm i webpack-parallel-uglify-plugin --D
plugins: [
...
new ParallelUglifyJsPlugin({
      cacheDir: '.cache/', // 开启缓存
      uglifyJS: {
        output: {
          // 最紧凑的输出
          beautify: false,
          // 删除注释
          comments: false,
        },
        warnings: false,
        compress: {
          // 在uglifyJS时删除么有用到的代码时不输出警告
          // 删除console
          drop_console: true,
          // 内嵌已经定义但是只用到一次的变量
          collapse_vars: true,
          // 提取出出现多次但是没有定义成变量去引用的静态值
          reduce_vars: true,
        },
      },
    }),
]

cnpm run build 会发现打包速度快了很多

优化前

优化后

在通过new ParallelUglifyPlugin()实例化时,支持以下参数。

  • · test:使用正则去匹配哪些文件需要被 ParallelUglifyPlugin 压缩, 默认为/.js$/,也就是默认压缩所有的.js文件。
  • · include:使用正则去命中需要被ParallelUglifyPlugin压缩的文件, 默认为[]。
  • · exclude:使用正则去命中不需要被ParallelUglifyPlugin压缩的文 件,默认为 []。
  • · cacheDir:缓存压缩后的结果,下次遇到一样的输入时直接从缓存 中获取压缩后的结果并返回。cacheDir用于配置缓存存放的目录路径。 默认不会缓存,若想开启缓存,则请设置一个目录路径。
  • · workerCount:开启几个子进程去并发执行压缩。默认为当前运行 的计算机的CPU核数减1。
  • · sourceMap:是否输出Source Map,这会导致压缩过程变慢。
  • · uglifyJS:用于压缩 ES5 代码时的配置,为 Object 类型,被原封不动地传递给UglifyJS作为参数。
  • · uglifyES:用于压缩 ES6 代码时的配置,为 Object 类型,被原封不动地传递给UglifyES作为参数。

其中的test、include、exclude与配置Loader时的思想和用法一样。

UglifyES(https://github.com/mishoo/UglifyJS2/tree/harmony)是 UglifyJS的变种,专门用于压缩ES6代码,它们都出自同一个项目,并 且不能同时使用。

UglifyES一般用于为比较新的 JavaScript运行环境压缩代码,例如用 于 ReactNative 的代码运行在兼容性较好的 JavaScriptCore 引擎中,为了 得到更好的性能和尺寸,可采用UglifyES压缩。

ParallelUglifyPlugin同时内置了UglifyJS和UglifyES,也就是说 ParallelUglifyPlugin支持并行压缩ES6代码。

安装成功后重新执行构建,会发现速度变快了许多。如果设置cacheDir 开启缓存,则在之后的构建中速度会更快。

使用自动刷新

文件监听是在发现源码文件发生变化时,自动重新构建出新的输出 文件。

Webpack官方提供了两大模块,一个是核心的 webpack(https://www.npmjs.com/package/webpack),webpack-dev-server。而文件监听功能是Webpack提供的。

 External

Externals用来告诉在Webpack要构建的代码中使用了哪些不用被打 包的模块,也就是说这些模板是外部环境提供的,Webpack在打包时可 以忽略它们。

假设:我们开发了一个自己的库,里面引用了lodash这个包,经过webpack打包的时候,发现如果把这个lodash包打入进去,打包文件就会非常大。那么我们就可以externals的方式引入。也就是说,自己的库本身不打包这个lodash,需要用户环境提供。

import _ from 'lodash';

配置externals

externals: {
  "lodash": {
        commonjs: "lodash",//如果我们的库运行在Node.js环境中,import _ from 'lodash'等价于const _ = require('lodash')
        commonjs2: "lodash",//同上
        amd: "lodash",//如果我们的库使用require.js等加载,等价于 define(["lodash"], factory);
        root: "_"//如果我们的库在浏览器中使用,需要提供一个全局的变量‘_’,等价于 var _ = (window._) or (_);
  }
}

有些JavaScript运行环境可能内置了一些全局变量或者模块,例如在 我们的HTML HEAD标签里通过以下代码引入jQuery:

<script src="path/jQuery.js"></script>

这时,全局变量jQuery就会被注入网页的JavaScript运行环境里。

如果想在使用模块化的源代码里导入和使用jQuery,则可能需要这 样:

import $ from 'jQuery'

构建后我们会发现输出的Chunk里包含的jQuery库的内容,这导致 jQuery库出现了两次,浪费加载流量,最好是Chunk里不会包含jQuery 库的内容。

Externals配置项就是用于解决这个问题的。

通过externals可以告诉Webpack在JavaScript运行环境中已经内置了 哪些全局变量,不用将这些全局变量打包到代码中而是直接使用它们。 要解决以上问题,可以这样配置externals:

module.export = {
  externals: {
    // 把导入语句里的 jquery 替换成运行环境里的全局变量 jQuery
    jquery: 'jQuery',
    react: 'React'
  }
}

不同环境设置externals方式

  1. 如果你的代码想运行在Node环境中,那么你需要在external中添加前缀commonjs2或者commonjs
externals:{
  react:'commonjs2 react',
  jquery:'commonjs2 jquery'
}

1.如果需要requirejs等符合AMD规范的环境中加载,那就要添加amd

externals:{
  react:'amd React',
  jquery:'amd jQuery'
}

2.如果要在浏览器中运行,那么不用添加什么前缀,默认设置就是global。

externals:{
  react:'React',
  jquery:'jQuery'
}

也可以这样

externals:["React","jQuery"]

这种方式配置下,就是配置你所引用你的库暴露出的全局变量。上面两种模式下或者说,如果你想运行代码在浏览器中,你所引用的包,必须暴露出一个全局变量。如果没有,这种方式不适合在浏览器下使用 。

externalslibraryTarget的关系

  • libraryTarget配置如何暴露 library。如果不设置library,那这个library就不暴露。就相当于一个自执行函数
  • externals是决定的是以哪种模式去加载所引入的额外的包
  • libraryTarget决定了你的library运行在哪个环境,哪个环境也就决定了你哪种模式去加载所引入的额外的包。也就是说,externals应该和libraryTarget保持一致。library运行在浏览器中的,你设置externals的模式为commonjs,那代码肯定就运行不了了。
  • 如果是应用程序开发,一般是运行在浏览器环境libraryTarget可以不设置,externals默认的模式是global,也就是以全局变量的模式加载所引入外部的库。

把模块中异步加载的组件都打包到一起 

了解更多,移步webpack专题分类专栏:https://blog.csdn.net/qq_41831345/category_9640180.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值