webpack——使用分析打包代码

Posted 日常充电

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了webpack——使用分析打包代码相关的知识,希望对你有一定的参考价值。

世上本无nodejs

js最初是在前端浏览器上运行的语言,js代码一旦脱离了浏览器环境,就无法被运行。直到nodejs的出现,我们在电脑上配置了node环境,就可以让js代码脱离浏览器,在node环境中运行。

浏览器不支持模块化

nodejs

nodejs可其他后端语言一样,支持模块化,享受了模块化的优点。

浏览器环境

可是浏览器并不支持nodejs的模块化语法

代码

//index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="./module1.js"></script>
</head>
<body>
    <div id="btn">我是一个标题</div>
</body>
</html>
//module1.js
const module2=require('./module2')
console.log('module2',module2)
//module2.js
const a=1
console.log('module2',a)
module.exports.a=a

 目录结构

效果

Uncaught ReferenceError: require is not defined  
[Five Server] connecting...
[Five Server] connected.

webpack使用

webpack.config.js配置

//webpack.config.js
const path=require("path")
module.exports=
    mode:'development',
    entry:"./module1.js",
    output:
        path:path.join(__dirname,"dist"),
        filename:"bundle.js"
    

重要配置项

入口(entry)、出口(output)、插件(plugins)、装载器(loader)、模式(mode)

运行结果

webpack打包代码

例一commonJs

模块代码

//module1.js
const module2=require('./module2')
console.log('module2',module2)

 

打包代码bundle.js

//打包代码稍作修正一(功能不改变)
(() => 

  var __webpack_modules__ = (
    "./module1.js":
    (__unused_webpack_module, __unused_webpack_exports, __webpack_require__) =>
      eval(`
          const module2=__webpack_require__("./module2.js");
          console.log('module2',module2.a)
      `);
    ,
    "./module2.js":
    (module) =>
      eval(`
          const a=1;console.log('module2',a);
          module.exports.a=a
      `);
    
  );

  var __webpack_module_cache__ = ;

  function __webpack_require__(moduleId) 
    var cachedModule = __webpack_module_cache__[moduleId];
    if (cachedModule !== undefined) 
      return cachedModule.exports;
    
    var module = __webpack_module_cache__[moduleId] = 
      exports: 
    ;
    __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
    return module.exports;
  

  var __webpack_exports__ = __webpack_require__("./module1.js");

 )();
//打包代码稍作修正二(功能不改变)
(function()
  var cache = ;

  function require(modulePath) 
    // var cachedModule = cache[moduleId];
    // if (cachedModule !== undefined) 
    //   return cachedModule.exports;
    // 
    // var module = cache[moduleId] = 
    //   exports: 
    // ;
    module=
      exports:
    
    modules[modulePath](module, module.exports, require);
    return module.exports;
  

  var modules=
    ["./module1.js"](module, exports, require)
      eval(`
          const module2=require("./module2.js");
          console.log('module2',module2.a)
      `);
    ,

    ["./module2.js"](module)
      eval(`
          const a=1;console.log('module2',a);
          module.exports.a=a
      `);
    
  

  var exports = require("./module1.js");
 )();

打包代码分析

①webpack实现了自己的require()函数

②webpack采用了立即执行函数

③webpac把各个模块的代码放到modules中

④各模块代码字符串形式存储,使用eval()函数执行

例二commonJs+ES6

模块代码

//module1.js
const module2=require('./module2')
console.log('module1',module2.a)
//module2.js
const module3=require('./utils/modules3')
console.log('module2',module3.a)
module.exports.a=module3.a
//module3.js
export const a=1
console.log('module3',a)

打包代码 

//打包代码稍作修正(功能不改变)
(() =>  
	var modules = (
    "./module1.js":
    ((module, exports, require) => 
        eval(`
          const module2=require("./module2.js");
          console.log('module1',module2.a)
        `);
    ),

    "./module2.js":
    ((module, exports, require) => 
        eval(`
            const module3=require('./utils/modules3.js');
            console.log('module2',module3.a);
            module.exports.a=module3.a
        `);
    ),

    "./utils/modules3.js":
    ((module, exports, require) => 
        "use strict";
        eval(`
              require.r(exports);
              require.d(exports, "a":() => (a));
              const a=1;
              console.log('module3',a)
        `);
    )
  );

	var _cache = ;
	
	function require(moduleId) 		
		var cachedModule = _cache[moduleId];
		if (cachedModule !== undefined) 
			return cachedModule.exports;
		
		var module = _cache[moduleId] = 	
			exports: 
		;
		modules[moduleId](module, module.exports, require);
		return module.exports;
	

	(() => 
		require.d = (exports, definition) => 
			for(var key in definition) 
				if(require.o(definition, key) && !require.o(exports, key)) 
					Object.defineProperty(exports, key,  enumerable: true, get: definition[key] );
				
			
		;
	)();
	
	(() => 
		require.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
	)();
	
	(() => 	
		require.r = (exports) => 
			if(typeof Symbol !== 'undefined' && Symbol.toStringTag) 
				Object.defineProperty(exports, Symbol.toStringTag,  value: 'Module' );
			
			Object.defineProperty(exports, '__esModule',  value: true );
      console.log(exports)
      // debugger
		;
	)();

	var exports = require("./module1.js");
)()
;

打包代码分析

较例一,require函数对象上添加了d、r、o三个函数,它们的功能分析。

解开debugger的注释,控制台打印出

Module __esModule: true, Symbol(Symbol.toStringTag): 'Module'
    __esModule: true
    Symbol(Symbol.toStringTag): "Module"
   [[Prototype]]: Object

分析r函数功能

r函数向exports对象添加__esModule、Symbol(Symbol.toStringTag)两个属性,来标注采用了ES6模块化

注释debugger,控制台打印出

分析d。r函数功能

d。r函数向exports对象添加a属性

例三ES6

模块代码

//module1.js
import a from './module2'
console.log('module1',a)
export const a=1
console.log('module2',a)

打包代码

(() => 
  "use strict";
  var modules = (

    "./module1.js":
      ((module, exports, require) => 

        eval(`require.r(exports);
              var _module2__WEBPACK_IMPORTED_MODULE_0__ = require("./module2.js");
              console.log('module1',_module2__WEBPACK_IMPORTED_MODULE_0__.a)
        `);
      ),

    "./module2.js":

      ((module, exports, require) => 

        eval(`require.r(exports);
              require.d(exports, "a": () => (a));
              const a=1;
              console.log('module2',a)`);

      )

  );
  
  var cache = ;

  function require(moduleId) 
    var cachedModule = cache[moduleId];
    if (cachedModule !== undefined) 
      return cachedModule.exports;
    

    var module = cache[moduleId] = 

      exports: 
    ;


    modules[moduleId](module, module.exports, require);


    return module.exports;
  


  (() => 

    require.d = (exports, definition) => 
      for (var key in definition) 
        if (require.o(definition, key) && !require.o(exports, key)) 
          Object.defineProperty(exports, key,  enumerable: true, get: definition[key] );
        
      
    ;
  )();


  (() => 
    require.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
  )();


  (() => 
    require.r = (exports) => 
      if (typeof Symbol !== 'undefined' && Symbol.toStringTag) 
        Object.defineProperty(exports, Symbol.toStringTag,  value: 'Module' );
      
      Object.defineProperty(exports, '__esModule',  value: true );
    ;
  )();
  var exports = require("./module1.js");

)()
  ;

代码分析

和上面的打包结果差不多。

webpack打包优化

 

 打包分析

 

1.初级分析:webpack内置的stats(构建的统计信息)

可以在 package.json 中使用 stats,也可以在 Node API 中使用 stats

webpack --config webpack.prod.js --json > stats.json

 

2.速度分析:speed-measure-webpack-plugin (分析整个打包总耗时&每个插件和loader的耗时情况)

 const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");

 const smp = new SpeedMeasurePlugin();

 const webpackConfig = smp.wrap({
   plugins: [
     new MyPlugin(),
     new MyOtherPlugin()
   ]
 });

 

3.体积分析:webpack-bundle-analyzer(分析依赖的第三方模块文件和业务里面的组件代码大小)

const BundleAnalyzerPlugin = require(‘webpack-bundle-analyzer‘).BundleAnalyzerPlugin;
 
 module.exports = {
   plugins: [
     new BundleAnalyzerPlugin()
   ]
 }

 

速度优化

 

1.使用高版本的webpack

webpack4 增加了一个叫mode的配置项

production默认值会提供一系列有效的默认值以便部署应用

optimization.splitChunks总是启用

 

2.多进程构建

happypack 每次 webapck 解析一个模块,HappyPack 会将它及它的依赖分配给 worker 线程中

 exports.plugins = [
    new HappyPack({
      id: ‘jsx‘,
      threads: 4,
      loaders: [ ‘babel-loader‘ ]
    }),
    new HappyPack({
      id: ‘styles‘,
      threads: 2,
      loaders: [ ‘style-loader‘, ‘css-loader‘, ‘less-loader‘ ]
    })
  ];

  exports.module.rules = [
    {
      test: /.js$/,
      use: ‘happypack/loader?id=jsx‘
    },
    {
      test: /.less$/,
      use: ‘happypack/loader?id=styles‘
    }
  ]

 

 

thread-loader:每次 webpack 解析一个模块,thread- loader 会将它及它的依赖分配给 worker 线程中

module.exports = {
    module: {
      rules: [
        {
          test: /.js$/,
          include: path.resolve("src"),
          use: [
          {
            loader: "thread-loader"
            options: {
                workers: 2  // worker的数量,默认是cpu核心数
        }
        }
       }
      ]
    }
  }

 

3.多进程并行压缩代码

terser-webpack-plugin 开启parallel参数

const TerserPlugin = require(‘terser-webpack-plugin‘);

 module.exports = {
   optimization: {
     minimize: true,
     minimizer: [
       new TerserPlugin({
         parallel: true
       })
     ]
   }
 };

 

4.预编译资源模块:使用 DLLPlugin 进行分包,  DllReferencePlugin manifest.json 引用

,react,react-dom,redux,react-redux等基础包和业务基础包打包成一个文件

 

webpack.dll.config.js文件

const path = require(‘path‘);

const webpack = require(‘webpack‘);

  module.exports = {
    context: process.cwd,
    resolve: {
      extensions: [‘.js‘, ‘.jsx‘, ‘.json‘, ‘.styl‘, ‘.css‘],
      modules: [__dirname, ‘node_modules‘]
    },
    entry: {
      vendor: [
        ‘react‘,
        ‘react-dom‘,
        ‘react-router-dom‘
      ]
    },
    output: {
      path: path.resolve(__dirname, ‘./dist/lib‘),
      filename: ‘[name].js‘,
      library: ‘[name]‘
    },
    plugins: [
      new webpack.DllPlugin({
        path: path.resolve(__dirname, ‘.‘, ‘[name]-manifest.json‘),
        name: ‘[name]‘
      })
    ]
  };

 

运行 webpack --config webpack.dll.config.js --mode production 生成vendor-manifest.json文件

 

webpack.config.js文件

module.exports = {
   plugins: [
    new webpack.DllReferencePlugin({
         manifest: require(‘./vendor-manifest.json‘)
       })
   ]
  }

 

html: <script type="text/javascript" src="./lib/vendor.js"></script>

 

5.基础库分离

 

(1)通过html-webpack-externals-plugin,然后在html里面直接引入组件库的cdn链接

const HtmlWebpackExternalsPlugin = require(‘html-webpack-externals-plugin‘)

  moudles.export = {

      plugins: [
          new HtmlWebpackExternalsPlugin({
              externals: [
                  {
                      module: ‘react‘,
                      entry: ‘//11.url.cn/now/lib/16.2.0/react.min.js‘,
                      global: ‘React‘
                  },
                  {
                      module: ‘react-dom‘,
                      entry: ‘//11.url.cn/now/lib/16.2.0/react-dom.min.js‘,
                      global: ‘ReactDom‘
                  }
              ]
          })
      ]
  } 

html:

<script type="text/javascript" src="https://11.url.cn/now/lib/16.2.0/react.min.js"></script>

<script type="text/javascript" src="https://11.url.cn/now/lib/16.2.0/react-dom.min.js"></script> 

 

(2) 通过webpack4SplitChunksPlugins(webpack3使用的是commonChunksPlugin)

module.exports = {
       plugins: [
           new HtmlWebpackPlugin({
               template: path.join(__dirname, `./src/pages/search/search.html`),
               filename: `search.html`,
               chunks: [‘vendors‘, ‘common‘, ‘search‘], //注意这里要引入vendors跟common
               inject: true,
               minify: {
                   html5: true,
                   collapseWhitespace: true,
                   preserveLineBreaks: false,
                   minifyCSS: true,
                   minifyJS: true,
                   removeComments: false
               }
           })
       ]
       optimization: {
           splitChunks: {
               minSize: 0,
               cacheGroups: {
                   vendors: {
                       test: /(react|react-dom)/,
                       name: ‘vendors‘,
                       chunks: ‘all‘,
                       priority: -10   // 需要设置权重才能都分离出来
                   },
                   common: {
                       name: ‘commons‘,
                       chunks: ‘all‘,
                       minChunks: 2,
                       priority: -20   
                   }
               }
           }
       }
   }

 

 

6.利用缓存:第一次构建花费正常的时间,第二次构建将显著加快

 

   babel-loader 开启缓存

module: {
    rules: [
      {
        test: /.js$/,
        exclude: ‘node_modules‘,
        use: {
          loader: ‘babel-loader‘,
          options: {
            cacheDirectory: true
          }
        }
      }
    ]
  }

 

 terser-webpack-plugin 开启缓存

module.exports = {
    optimization: {
      minimize: true,
      minimizer: [
        new TerserPlugin({
          cache: true
        })
      ]
    }
  }

 

使用 cache-loader 或者 hard-source-webpack-plugin

module.exports = {
    module: {
      rules: [
        {
          test: /.js$/,
          use: [
            ‘cache-loader‘,
            ‘babel-loader‘
          ],
          include: path.resolve(‘src‘)
        }
      ]
    }
  }

 

7.缩小构建目标(尽可能少的构建模块)

 1babel-loader不解析node-modules

exclude: "node-modules"

(2)减少文件搜索范围

优化 resolve.modules 配置(减少模块搜索层级) 

优化 resolve.mainFields 配置

优化 resolve.extensions 配置

合理使用 alias(模块别名相对于当前上下文导入)

module: {
    resolve: {
      alias: {
        react: path.resolve(__dirname, ‘./node_modules/react/dist/react.min.js‘)
      },
      modules: [path.resolve(__dirname, ‘node_modules‘)],
      extensions: [‘.js‘],
      mainFields: [‘main‘]
    }
  }

 

体积优化

 

1.Scope Hoisting 

将所有模块的代码按照引?顺序放在?个函数作?域里,然后适当的重命名?些变量以防?变量名冲突

  webpack4  mode production 默认开启

  new webpack.optimize.ModuleConcatenationPlugin()

 

2.使用Tree shaking擦除无用的javaScriptcss

概念:1 个模块可能有多个方法,只要其中的某个方法使用到了,则整个文件都会被打到 bundle ?去,tree shaking 就是只把用到的方法打? bundle ,没?到的方法会在 uglify 阶段被擦除掉。

使?:webpack production mode的情况下默认开启

要求:必须是 ES6 的语法,CJS 的方式不支持

 

 css:purgecss-webpack-plugin mini-css-extract-plugin 配合使用

 

3.图片压缩

配置image-webpack-loader

 loader: "image-webpack-loader"

 

4.使用动态polyfill-service或者browserlist

   根据浏览器的UA来判断当前浏览器缺失哪些特性,进而进行补强

 

 

 

以上是关于webpack——使用分析打包代码的主要内容,如果未能解决你的问题,请参考以下文章

webpack打包原理

webpack——使用分析打包代码

webpack打包优化

webpack-打包优化方案

webpack

webpack提高打包速度