webpackwebpack术语 ==bundle==bundle是webpack打包的最终产物,webpack从入口文件开始构建所以依赖模块,得到这么一个或多个bundle文件。可以被浏览器直接加载使用
==chunk== chunk是webpack内部处理模块时的一个中间表示,是代码块的集合。bundle由chunk组成。webpack中的chunk有以下几种类型:
Entry Chunk:基于entry配置项
Normal Chunk:通常是由webpack的代码拆分功能生成的chunk,比如使用import动态导入的模块
Initial Chunk:包含初始加载需要的模块,是entry chunk本身或者从中拆分出来
Async Chunk:包含异步加载模块,通常是import()生成的
Vendor Chunk:包含来自node_modules的模块,通常是第三方库
==tree shaking == 通过分析导入代码的使用情况,决定实际使用到的那些依赖性,删除未使用的部分
webpack 做了什么
打包工具,一切静态资源皆可打包。webpack会分析项目结构,找到js模块以及一些其他浏览器不能直接运行的拓展语言比如typescript,将其打包为合适的格式供浏览器使用webpack运行在nodejs中
webpack的作用webpack就像生产线,源文件经过系列处理流程后转换成输出结果,webpack通过Tapable来组织这条生产线,webpack在运行过程中会广播事件。
一次完整的webpack内部执行流程
将命令行参数和webpack配置文件合并解析,得参数对象,传给webpack执行得到compiler对象。
使用compiler对象的run方法开始编译,每次run编译都会生成一个compilation对象。
触发compiler对象的make方法,分析入口文件,调用compilation的buildModule方法创建主模块对象
生成入口文件Ast(抽象语法树),通过Ast分析和递归加载依赖模块,根据文件类型和loader配置对模块进行处理,生成模块依赖图
所有模块分析完成,执行compilation的seal方法对每个chunk进行整理、优化、封装。
最后执行compiler的emitAsset方法把生成的文件输出到output目录中。
loader webpack使用loader来处理文件。webpack只能理解JavaScript和JSON文件。比如说,将less用less-loader 转换为css,将ts用ts-loader转换为js等等。
loader是一个导出为function的node模块,它描述了webpack如何处理非js模块 ,将匹配到的文件一次转换,可以链式传递转换。配置loader的方式有:
在webpack.config.js配置文件中配置loader
命令行参数方式:webpack –module-bind ‘txt=raw-loader’
内联使用:1 2 3 import xxx from 'raw-loader!./file.txt' require ('style-loader!css-loader?minimize~./main.js' )
配置示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 rules : [ { *test : '/\.css' , *use : ['style-loader' , 'css-loader' ], exclude : /node_modules/ , include : ['xxx' ], options : {} }, { test : '/\.(mp4|webm|wav)(\?.*)?$/' , use : [ { loader : 'url-loader' , options : { limit : 10240 , fallback : { loader : 'file-loader' , options : { name : 'img/[name].[hash.8].[ext]' } } }, } ], include : ['assets/' ], exclude : }, ]
==常用loader: ==
babel-loader
css相关:css/style/less/postcss-loader
File-loader:将文件处理后,移动到输出目录中
url-loader:一般与file-loader搭配使用。如果文件小于限制的大小,返回base64编码,否则使用file-loader将文件移动到输出目录中
css-loader:支持css modules,使用方法为在loader配置中写为css-loader?module
plugin webpack plugin是一个具有 apply 属性的js对象。plugin比loader更强大,它是webpack的核心功能,在webpack运行过程中,webpack会广播事件,plugin只需要监听它关心的事件,就能加入这条生产线。plugin通过钩子可以涉及整个构建流程,可以做一些构建范围内的事情。从代码来看,webpack编译代码过程中,会触发一系列tapable钩子事件,plugin做的事就是找到这些钩子,往上面挂上自己的任务,即注册事件,这样注册的事件就会随着钩子的触发而执行
webpack内置的plugin 有:
uglifyJsPlugin,用于压缩和混淆代码;
commonChunkPlugin,用于将第三方库和业务代码分开打包,提高打包效率。通过plugin可以访问compiler和complilation过程,通过钩子可拦截webpack的执行。
下面列举一些有用的webpack plugin
==htmlWebpackPlugin== 会生成默认的index.html文件,会替换原来的html文件
==WebpackManifestPlugin==webpack使用manifest追踪所有 模块 -> 输出 之间的映射,此插件可以将manifest数据提取为json文件
配置示例:
1 2 3 4 5 6 const BundleAnalyzerPlugin = require ('webpack-bundle-analyzer' ).BundleAnalyzerPlugin ;... plugins : [ new BundleAnalyzerPlugin (); ]
==loader比较 plugin== loader是转换器,非js模块 => js模块(常见)、js => js(较少,比如babel-loader/typescript-loader)、非js => 非js(少见) plugin负责在特定的生命周期执行任务,能力更广泛一些
module 在webpack中一切都是module,webpack支持的模块包括,ES2015模块,使用import加载;commonJs模块,使用require加载;AMD模块,使用require加载,
webpack可配置项 入口文件 常见的入口配置: 分离app和第三方库,这样的配置可以将第三方库单独打包成chunk,浏览器可以独立缓存他们,这种做法适合在webpack<4的版本中使用,在webpack>=4的版本,建议使用optimization.splitChunks将vendor和app应用程序分开,而不要单独设置entry
1 2 3 4 entry : { main : './src/main.js' , vendor : ['vue' , 'vue-router' , 'xxx' ] }
多页面应用程序。在多页面应用程序中,server 会拉取一个新的 HTML 文档给你的客户端。页面重新加载此新文档,并且资源被重新下载。然而,这给了我们特殊的机会去做很多事,例如使用 optimization.splitChunks为页面间共享的应用程序代码创建 bundle。由于入口起点数量的增多,多页应用能够复用多个入口起点之间的大量代码/模块,从而可以极大地从这些技术中受益。
1 2 3 4 5 entry : { pageOne : './src/page1.js' , pageTwo : './src/page2.js' , pageThree : './src/page3.js' , }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 const path = require ('path' );const plugin = require ('' );module .exports = { # 入口文件 entry : { index : './src/index.js' ; }, devtools : 'inline-source-map' , # 输出文件 output : { filename : '[name].js' , chunkFilename : '' , path : path.resolve (__dirname, 'dist' ), publicPath : 'https://cdn.example.com/public/xxx' , crossOriginLoading : '' , libraryTarget : '' , libraryExport : '' , clean : true , }, module : { rules : [ { test : /\.css|scss$/ , include : '' , exclude : '' , use : ['babel-loader?cacheDirectory' , 'style-loader' , 'css-loader' , 'scss-loader' ], } ] }, noParse : [ /xxx.js/ , ] resolve : { alias : { components : './src/components/' , 'react$' : '/' , }, extensions : ['.js' , '.jsx' ], modules : ['./src/components' , 'node_modules' ], enforceExtension : false , enforceModuleExtension : false , } # 插件,重点在这些plugin的配置项 plugin : [ new plugin ({ name : '' }) ] }
webpack 模块加载原理从一个简单的例子开始(webpack版本5),入口文件是index.js,在index.js中引入了三个模块
1 2 3 4 5 6 import "./default.css" ;import module from './test' ;import test2 from './test2' ;console .log (module .name );
1 2 3 4 body { background-color : aqua; }
1 2 3 4 module .exports = { name : 'kafka' , }
1 2 3 4 5 export default function f2 ( ) { return 'Alice' ; } export function ff ( ) {}
打包出来的文件为:
fold 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 (() => { var __webpack_modules__ = { "./src/default.css" : (module , __webpack_exports__, __webpack_require__ ) => { "use strict" ; eval ( `__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ }); /* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__( /*! ../node_modules/css-loader/dist/runtime/noSourceMaps.js */ "./node_modules/css-loader/dist/runtime/noSourceMaps.js" ); /* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ ); /* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__( /*! ../node_modules/css-loader/dist/runtime/api.js */ "./node_modules/css-loader/dist/runtime/api.js" ); /* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);\n// Imports var ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default())); // Module ___CSS_LOADER_EXPORT___.push([module.id, "body {\\n background-color: aqua;\\n}", ""]); // Exports\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___); //# sourceURL=webpack://my-webpack/./src/default.css?` ); }, "./node_modules/css-loader/dist/runtime/api.js" : (module ) => { "use strict" ; eval ( '\n\n/*\n MIT License http://www.opensource.org/licenses/mit-license.php\n Author Tobias Koppers @sokra\n*/\nmodule.exports = function (cssWithMappingToString) {\n var list = []; // return the list of modules as css string\n\n list.toString = function toString() {\n return this.map(function (item) {\n var content = "";\n var needLayer = typeof item[5] !== "undefined";\n\n if (item[4]) {\n content += "@supports (".concat(item[4], ") {");\n }\n\n if (item[2]) {\n content += "@media ".concat(item[2], " {");\n }\n\n if (needLayer) {\n content += "@layer".concat(item[5].length > 0 ? " ".concat(item[5]) : "", " {");\n }\n\n content += cssWithMappingToString(item);\n\n if (needLayer) {\n content += "}";\n }\n\n if (item[2]) {\n content += "}";\n }\n\n if (item[4]) {\n content += "}";\n }\n\n return content;\n }).join("");\n }; // import a list of modules into the list\n\n\n list.i = function i(modules, media, dedupe, supports, layer) {\n if (typeof modules === "string") {\n modules = [[null, modules, undefined]];\n }\n\n var alreadyImportedModules = {};\n\n if (dedupe) {\n for (var k = 0; k < this.length; k++) {\n var id = this[k][0];\n\n if (id != null) {\n alreadyImportedModules[id] = true;\n }\n }\n }\n\n for (var _k = 0; _k < modules.length; _k++) {\n var item = [].concat(modules[_k]);\n\n if (dedupe && alreadyImportedModules[item[0]]) {\n continue;\n }\n\n if (typeof layer !== "undefined") {\n if (typeof item[5] === "undefined") {\n item[5] = layer;\n } else {\n item[1] = "@layer".concat(item[5].length > 0 ? " ".concat(item[5]) : "", " {").concat(item[1], "}");\n item[5] = layer;\n }\n }\n\n if (media) {\n if (!item[2]) {\n item[2] = media;\n } else {\n item[1] = "@media ".concat(item[2], " {").concat(item[1], "}");\n item[2] = media;\n }\n }\n\n if (supports) {\n if (!item[4]) {\n item[4] = "".concat(supports);\n } else {\n item[1] = "@supports (".concat(item[4], ") {").concat(item[1], "}");\n item[4] = supports;\n }\n }\n\n list.push(item);\n }\n };\n\n return list;\n};\n\n//# sourceURL=webpack://my-webpack/./node_modules/css-loader/dist/runtime/api.js?' ); }, "./node_modules/css-loader/dist/runtime/noSourceMaps.js" : (module ) => { "use strict" ; eval ( "\n\nmodule.exports = function (i) {\n return i[1];\n};\n\n//# sourceURL=webpack://my-webpack/./node_modules/css-loader/dist/runtime/noSourceMaps.js?" ); }, "./src/index.js" : ( __unused_webpack_module, __webpack_exports__, __webpack_require__ ) => { "use strict" ; eval ( '__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _default_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./default.css */ "./src/default.css");\n/* harmony import */ var _test__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./test */ "./src/test.js");\n/* harmony import */ var _test__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_test__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _test2__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./test2 */ "./src/test2.js");\n\n\n\n\nconsole.log((_test__WEBPACK_IMPORTED_MODULE_1___default().name));\n\n//# sourceURL=webpack://my-webpack/./src/index.js?' ); }, "./src/test.js" : (module ) => { eval ( "module.exports = {\n name: 'kafka'\n}\n\n//# sourceURL=webpack://my-webpack/./src/test.js?" ); }, "./src/test2.js" : ( __unused_webpack_module, __webpack_exports__, __webpack_require__ ) => { "use strict" ; eval ( "__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ f2)\n/* harmony export */ });\nfunction f2() {\n return 'Alice';\n}\n\n//# sourceURL=webpack://my-webpack/./src/test2.js?" ); }, }; 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] = { id : moduleId, exports : {}, }); __webpack_modules__[moduleId](module , module .exports , __webpack_require__); return module .exports ; } (() => { __webpack_require__.n = (module ) => { var getter = module && module .__esModule ? () => module ["default" ] : () => module ; __webpack_require__.d (getter, { a : getter }); return getter; }; })(); (() => { __webpack_require__.d = (exports , definition ) => { for (var key in definition) { if ( __webpack_require__.o (definition, key) && !__webpack_require__.o (exports , key) ) { Object .defineProperty (exports , key, { enumerable : true , get : definition[key], }); } } }; })(); (() => { __webpack_require__.o = (obj, prop ) => Object .prototype .hasOwnProperty .call (obj, prop); })(); (() => { __webpack_require__.r = (exports ) => { if (typeof Symbol !== "undefined" && Symbol .toStringTag ) { Object .defineProperty (exports , Symbol .toStringTag , { value : "Module" }); } Object .defineProperty (exports , "__esModule" , { value : true }); }; })(); var __webpack_exports__ = __webpack_require__ ("./src/index.js" ); })();
可以看到打包后出来的文件是一个立即执行函数,这个立即执行函数内做的事情有:
__webpack_modules__对象包含了所有打包好的模块,以文件路径为key,文件内容为value
定义一个模块缓存对象__webpack_module_cache__
定义一个模块加载函数 __webpack_require__
??
使用__webpack_require__加载入口模块,赋值给__webpack_exports__
__webpack_require__函数函数的内容如下:
fold 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function __webpack_require__ (moduleId ) { var cachedModule = __webpack_module_cache__[moduleId]; if (cachedModule !== undefined ) { return cachedModule.exports ; } var module = (__webpack_module_cache__[moduleId] = { id : moduleId, exports : {}, }); __webpack_modules__[moduleId](module , module .exports , __webpack_require__); return module .exports ; }
首先判断要加载的模块是否在缓存中,如果是直接返回
否则新建一个模块module,并放入模块缓存中,module的key为moduleId,内容是{ id: moduleId, exports: {} },加入缓存时exports的值固定为空对象,moduleId就是文件的路径名
执行模块函数,执行时传入了三个参数,分别是module,module.exports,__webpack_require__函数
返回模块的exports对象
__webpack_modules__中入口函数打包后的内容(处理了下,eval作为参数的代码应该用引号包裹成字符串格式):
1 2 3 4 "./src/index.js" : (__unused_webpack_module, __webpack_exports__, __webpack_require__ ) => { "use strict" ; eval ('文件内容在下一个👇🏻' ); }
1 2 3 4 5 6 7 8 9 __webpack_require__.r (__webpack_exports__);\n var _default_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__ ("./src/default.css" );var _test__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__ ("./src/test.js" ); var _test__WEBPACK_IMPORTED_MODULE_1___default = __webpack_require__.n (_test__WEBPACK_IMPORTED_MODULE_1__);var _test2__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__ ("./src/test2.js" );console .log ((_test__WEBPACK_IMPORTED_MODULE_1___default ().name ));(0 ,_test2__WEBPACK_IMPORTED_MODULE_2__["default" ])();
__webpack_require__.r对第二个参数__webpack_exports__的处理用的是__webpack_require__.r函数,看下这个函数是如何定义的:
1 2 3 4 5 6 7 8 9 __webpack_require__.r = (exports ) => { if (typeof Symbol !== "undefined" && Symbol .toStringTag ) { Object .defineProperty (exports , Symbol .toStringTag , { value : "Module" , }); } Object .defineProperty (exports , "__esModule" , { value : true }); };
可以看出__webpack_require__.r的作用是给__webpack_exports__增加一个__esModule = true属性,表示这个模块是es6模块。在遇到import导入或者export导出时,就会使用这个函数。其作用是为了处理混合使用CommonJS和ES6 module的情况(上面的代码就是这种情况,在test.js中用module.exports导出模块,在index.js中用import导入模块)
__webpack_require__.d1 2 3 4 5 6 7 8 9 10 11 12 13 14 __webpack_require__.d = (exports , definition ) => { for (var key in definition) { if ( __webpack_require__.o (definition, key) && !__webpack_require__.o (exports , key) ) { Object .defineProperty (exports , key, { enumerable : true , get : definition[key], }); } } };
作用是给__webpack_exports__增加属性
可以看到在打包后的test2模块用到了__webpack_require__.d函数.根据test2模块内容导出的内容为__webpack_exports__增加属性,对于exprot default function f2的导出方式,赋值default = f2。而对于export function f2,赋值[ff] = ff
1 2 3 4 5 6 7 8 9 10 11 12 13 "./src/test2.js" : (__unused_webpack_module, __webpack_exports__, __webpack_require__ ) => { "use strict" ; eval ('' __webpack_require__.r (__webpack_exports__); __webpack_require__.d (__webpack_exports__, { "default" : () => (f2), "ff" : () => (ff) }); function f2 ( ) {return \'Alice\';} function ff() {} //# sourceURL=webpack://my-webpack/./src/test2.js? ' '); }
_webpack_require__.n1 2 3 4 5 6 __webpack_require__.n = (module ) => { var getter = module && module .__esModule ? () => module ["default" ] : () => module ; __webpack_require__.d (getter, { a : getter }); return getter; };
判断该模块是否是es6模块,是则返回module[‘default’],否则返回module,返回之前为返回值增加getter属性{a: getter}
搞懂webpack hmr 热更新 热更新(HMR,Hot ModuleReplacement),指的是当对代码做了修改并保存好后,webpack会自动对代码进行重新打包,将改动的模块发送到浏览器端,替换对应的旧模块,实现局部更新页面。
在webpack.config.js内配置启用hmr:
1 2 3 4 5 devServer : { hot : true , }
HMR原理 …todo
webpack分包 权衡拆包和并包,是webpack分包的重点 chunks: 表示从哪些chunks里抽取代码,有三个值:
initial:初始块,分开打包异步/非异步模块
async:按需加载块, 类似initial,但是不会把同步引入的模块提取到vendors中
all:全部块,无视异步/非异步,如果有异步,统一为异步,也就是提取成一个块,而不是放到入口文件打包内容中
sourcemap sourcemap的作用是标识打包前后bundle和源码的对应关系
webpack常用包 webpack-node-externals 配置在externals选项,externals选项用于排除某些import的包打包到bundle中,webpack-node-externals能够排除node_modules中的所有模块,node模块将被保留为require('module'),插件用法如下:
1 npm i webpack-node-externals --save-dev
1 2 3 4 5 6 7 8 9 const nodeExternals = require ('webpack-node-externals' )module .exports = { target : 'node' , externalsPresets : {node : true }, externals : [nodeExternals] }
postcss-px2rem-include 项目中写移动端适配时用到了这个包,它是在postcss-px2rem基础上加了一个include选项。使用的时候,在postcss.config.js中的plugins选项增加:
1 2 3 4 require ('postcss-px2rem-include' )({ remUnit : 37.5 , include : /download-index-m|download-album-m/i })
用正则去匹配文件名,命中的文件,对其字符串格式的css转换为rem格式并进行替换,调用的是Px2rem的generateRem方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 module .exports = postcss.plugin ( 'postcss-px2rem-include' , function (options ) { return function (css, result ) { if (options.include && css.source .input .file .match (options.include ) !== null ) { var oldCssText = css.toString (); var px2remIns = new Px2 rem(options); var newCssText = px2remIns.generateRem (oldCssText); result.root = postcss.parse (newCssText) } else { result.root = css; } } });
下面进到px2rem看一下它是如何转换px为rem的。进入px2rem/lib/px2rem.js文件,可以在Px2rem的原型上找到generateRem方法,入参是string类型的cssText,转换为css ast后处理,主要函数是processRules,这里有一个css ast的概念,css抽象语法树,它和javascript的ast是一样的,用树的形式表示代码的语法结构。例如,下面是一段css代码及其对应的css ast
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @media screen and (min-width : 480px ) { body { background-color : lightgreen; } } @keyframes rotate { from { transform : rotate (0 ); } to { tranfrom: rotate (180deg ); } } #main { width : 100px ; height : 100px ; border : 1px solid black; } ul li { padding : 5px ; }
对应的ast为 借助上面的ast作为参考,看下面这段processRules代码:
fold title:function_processRules 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 function processRules (rules, noDealPx ) { for (var i = 0 ; i < rules.length ; i++) { var rule = rules[i]; if (rule.type === 'media' ) { processRules (rule.rules ); continue ; } else if (rule.type === 'keyframes' ) { processRules (rule.keyframes , true ); continue ; } else if (rule.type !== 'rule' && rule.type !== 'keyframe' ) { continue ; } if (!noDealPx) { var newRules = []; for (var dpr = 1 ; dpr <= 3 ; dpr++) { var newRule = {}; newRule.type = rule.type ; newRule.selectors = rule.selectors .map (function (sel ) { return '[data-dpr="' + dpr + '"] ' + sel; }); newRule.declarations = []; newRules.push (newRule); } } var declarations = rule.declarations ; for (var j = 0 ; j < declarations.length ; j++) { var declaration = declarations[j]; if (declaration.type === 'declaration' && pxRegExp.test (declaration.value )) { var nextDeclaration = rule.declarations [j + 1 ]; if (nextDeclaration && nextDeclaration.type === 'comment' ) { if (nextDeclaration.comment .trim () === config.forcePxComment ) { if (declaration.value === '0px' ) { declaration.value = '0' ; declarations.splice (j + 1 , 1 ); continue ; } if (!noDealPx) { for (var dpr = 1 ; dpr <= 3 ; dpr++) { var newDeclaration = {}; extend (true , newDeclaration, declaration); newDeclaration.value = self._getCalcValue ('px' , newDeclaration.value , dpr); newRules[dpr - 1 ].declarations .push (newDeclaration); } declarations.splice (j, 2 ); j--; } else { declaration.value = self._getCalcValue ('rem' , declaration.value ); declarations.splice (j + 1 , 1 ); } } else if (nextDeclaration.comment .trim () === config.keepComment ) { declarations.splice (j + 1 , 1 ); } else { declaration.value = self._getCalcValue ('rem' , declaration.value ); } } else { declaration.value = self._getCalcValue ('rem' , declaration.value ); } } } if (!rules[i].declarations .length ) { rules.splice (i, 1 ); i--; } if (!noDealPx) { if (newRules[0 ].declarations .length ) { rules.splice (i + 1 , 0 , newRules[0 ], newRules[1 ], newRules[2 ]); i += 3 ; } } } }
除了提供px -> rem这个转换,px2rem还有一个生成x1, x2, x3版本以适配设备dpr的样式,接下来看看这个怎么做的
fold title:适配dpr_x1_x2_x3 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 Px2 rem.prototype .generateThree = function (cssText, dpr ) { dpr = dpr || 2 ; var self = this ; var config = self.config ; var astObj = css.parse (cssText); function processRules (rules ) { for (var i = 0 ; i < rules.length ; i++) { var rule = rules[i]; if (rule.type === 'media' ) { processRules (rule.rules ); continue ; } else if (rule.type === 'keyframes' ) { processRules (rule.keyframes ); continue ; } else if (rule.type !== 'rule' && rule.type !== 'keyframe' ) { continue ; } var declarations = rule.declarations ; for (var j = 0 ; j < declarations.length ; j++) { var declaration = declarations[j]; if (declaration.type === 'declaration' && pxRegExp.test (declaration.value )) { var nextDeclaration = rule.declarations [j + 1 ]; if (nextDeclaration && nextDeclaration.type ==='comment' ) { if (nextDeclaration.comment .trim ()===config.keepComment ) { declarations.splice (j + 1 , 1 ); continue ; } else if (nextDeclaration.comment .trim () === config.forcePxComment ) { declarations.splice (j + 1 , 1 ); } } declaration.value = self._getCalcValue ('px' , declaration.value , dpr); } } } } processRules (astObj.stylesheet .rules ); return css.stringify (astObj); };
==HtmlWebpackPlugin== 是webpack打包的常用插件,用于生成和修改HTML文件,能够根据模板生成html文件,并自动将打包得到的js/css等资源注入html文件中
==miniCssExtractPlugin== 用于将css从js文件中提取出来,生成独立的css文件,经过css-loader处理过后的css文件内联在js文件中,用此插件将其提取为css文件,减小js文件大小,可提高性能
webpack-bundle-analyzer 一个plugin,将bundle内容展示为一个可视化交互式树状图
postcss 可以看做是一个平台,插件在上面跑,它提供了AST解析器,postcss+插件能帮助我们处理css
html-webpack-plugin uglyfyjs-webpack-plugin webpack VS rollup 一般来说,webpack更合适与大一点的业务,能够提供兼容性保障,webpack在打包时会注入很多自己的代码,将commonJS和esm都实现为自己的require,见上面的模块化 rollup则更适合用于一些工具SDK的打包,打包出来的内容比较纯净,并且ESM更容易进行tree-shaking
bundle VS bundleless
实际场景问题记录 项目打包占用内存过多,超出了node v8引擎默认的最大内存,打包失败问题 一次有效的解决方式:修改最大使用内存量 两种方式:一种是在启动脚本内加--max-old-space-size=4096; 另一种是通过修改变量NODE_OPTIONS全局设置node v8引擎最大内存使用量 export NODE_OPTIONS=--max-old-space-size=4096
参考资料
带你入门前端工程-构建工具-webpack