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) 通过webpack4的SplitChunksPlugins(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.缩小构建目标(尽可能少的构建模块)
(1)babel-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擦除无用的javaScript和css
概念: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——使用分析打包代码的主要内容,如果未能解决你的问题,请参考以下文章