webpack 执行过程
start
npm install webpack webpack-cli -D
notes
- npx 可以直接运行node_modules/.bin 目录下的命令
- 使用loader
// 1 直接用
require("expose-loader?$!jquery");
// 2 配置module
use: 'file-loader'
use: [
{ loader: 'style-loader' },
{
loader: 'css-loader',
options: {
modules: true
}
},
{ loader: 'sass-loader' }
]
- file-loader 解析 import和require文件为一个路径 ,并把文件emit到输出目录
{
test: /\.(png|jpe?g|gif)$/i,
loader: 'file-loader',
options: {
outputPath: 'images',
},
},
- url-loader which transforms files into base64 URIs.
jquery 引入方式
- script 标签
- entry 加一个vendor
new webpack.ProvidePlugin({ $ : 'jquery' })
- dll-plugin
- expose-loader 只是把变量暴露到全局,打包不会代码分割
require("expose-loader?$!jquery");
module: {
rules: [{
test: require.resolve('jquery'),
use: [{
loader: 'expose-loader',
options: 'jQuery'
},{
loader: 'expose-loader',
options: '$'
}]
}]
}
sorcemap
webpack在开发模式时,可以加
devtool: 'inline-source-map'
开启这个配置,快速定位代码出错文件和位置
代码自动编译
3种可以自动编译源代码的实现
- webpack’s Watch Mode,这种需要手动刷新页面
- webpack-dev-server,这种可以自动刷新页面
- webpack-dev-middleware
代码分割
代码分割可以吧代码分成不同的bundle,来实现按需加载和并行加载,实现方式有3种,
- 多入口(多入口会造成代码库重用,比如都用了jQuery,那么jq都会被打包在入口文件里)
- SplitChunksPlugin
// webpack 配置
const path = require("path");
module.exports = {
mode: 'development',
entry:{
index: './src/index',
main: './src/main',
},
output:{
path: path.join(__dirname, 'dist'),
filename: '[name].bundle.js'
},
optimization: {
splitChunks: {
chunks: 'all'
}
},
module:{
},
}
// 这样就会把jquery打包成一个单独的chunk
// mini-css-extract-plugin 和 bundler-loader也是实现这种效果的
- 动态加载 dymamic import
- CommonsChunkPlugin在4版本已经移除了,要使用SplitChunksPlugin配置,如下
devserver
devserver 不输出任意编译文件,而是就像位置在服务器根目录的真实文件,但是在内存中 ,内置了webpack-dev-middle中间件,可以独立使用,也可以放到express当做中间件
// 打包的文件放在内存里
devServer: {
contentBase: './dist', 静态资源根目录
host: 'localhost',
port: 8888,
compress: true,
open: true
}
url-loader
可以把文件变成base64,options的limit是小于多大会被搞成base64,fallback是大于这个大小是谁处理,默认是file–loader
file-loader
默认会把文件,处理成带hash的名字,根据自身的内容,可以在options里配置name选项
options: {
outputPath: 'images',
name: ''
}
提取css
如果样式都放在style标签,html文件过大加载慢 ,使用mini-css-webpack-plugin替代extract-text-webpack-plugin,好处是可以异步记载和防止重复编译
optimization
optimization : {
// 和 runtimeChunk : 'single' 效果一样
// 创建一个单独的chunk 共享给所有的生成的chunk
runtimeChunk: {
name: 'runtime'
}
}
resolve
resolve : {
alias: {},
modules: [], //配置除了在node_modules里,还在那里找包
mainFields: ['main', 'lib'] // 默认会找package.json 里main的字段
}
多页面
可以在htmlWebpackPlugin 里,配置多个模板。然后加载entry对应的chunk的名字
dll
- dll 引用其实最后是 key是
"./node_modules/_react@16.13.0@react/index.js":
- value 是 webpack_require(libraryName)(id) 在manifest里有一个对象 有这个id和"./node_modules/_react@16.13.0@react/index.js": ```这个路径
// webpack.dll.js
module.exports = {
entry : {
vendor: ['react', 'react-dom', 'axios']
},
output:{
path: path.join(__dirname, 'dist'),
filename: '[name].dll.js',
library: '[name]_library',
// 默认是var,还有commonjs commonjs2
libraryTarget: "var"
},
plugins:[
new webpack.DllPlugin({
path: path.join(__dirname, 'dist', '[name]-manifest.json'),
name: '[name]_library',
context: __dirname
})
]
}
// webpack.config.js
plugins:[
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./dist/vendor-manifest.json')
}),
]
把一个模块单独打包
// webpack.dll.js
const webpack = require("webpack")
const path = require("path");
module.exports = {
entry : "./src/lib.js",
mode: 'production',
output:{
path: path.join(__dirname, 'dist'),
filename: 'lib.dll.js',
library: 'hello',
libraryTarget: "var"
},
}
plugin happypack
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
loader: 'happypack/loader?id=babel'
}
]
},
plugins: [
new HappyPack({
id: 'babel',
loader: 'babel-loader'
}),
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./dist/vendor-manifest.json')
}),
]
ParallelUglifyPlugin 已废弃 使用terser-webpack-plugin
区分不同环境
webpack 内置了区分,只要在optimization 中加nodeEnv, 在开发中就可以直接使用
process.env.NODE_ENV
// 设置不同环境
// 1. package.json 里
const conf = {
"build" : "cross-env NODE_ENV=production && webpack",
"start" : "cross-env NODE_ENV=development && webpack"
}
// 2 抽离webpack.dev.js 和 webpack.production.js 出一个webpack.base.js
// 3. webpack.config.js
const base = require("./webpack.base");
const merge = require("webpack-merge");
const env = process.env.NODE_ENV;
let config;
if(env === 'development') {
config = require("./webpack.dev")
}else {
config = require("./webpack.prod")
}
module.exports = merge(base, config)
process
在开发中process,最后是module.exports ,引用的是 process 库
tree shaking
{
"presets": [
[
"@babel/env",
{
"targets": {
"chrome": "60",
"ie" : 9
},
"corejs": {
"version": 3
},
"useBuiltIns": "usage",
// 不编译为 es5 保持es6 模块化
"modules": false
}
],
]
}
eslint
{
test: /\.js/,
loader: 'eslint-loader',
enforce: 'pre'
}
打包优化
- parallel-webpack 多页面打包
- cache-loader 缓存
- 生产环境,Thread-loader 打包
- 如编译js,加include 和 exclude
- 开发环境把optimization 关闭
optimization: {
removeAvailableModules: false,
removeEmptyChunks: false,
splitChunks: false,
moduleIds: hashed // 可以让一些库 ( 代码不变)的hash值不变
// @depracated uglifyjs 优化配置
minimizer: [
new UglifyJsPlugin({
parallel: true,
cache: true,
uglifyOptions: {
compress: {
warnings: false,
ie8: false,
drop_debugger: true,
drop_console: true
},
output: {
comments: false,
},
}
})
],
// terser 配置
const optimization = {
minimize: true,
minimizer: [
new TerserPlugin({
cache: true,
parallel: true,
terserOptions: {
warnings: false,
ie8: false,
safari10: false,
},
output: {
comments: false,
}
}),
],
}
}
- runtimechunk是运行时的chunk
简单源码
// chunk 0
const module = {
"./src/app3/video.js": (function (module, __webpack_exports__, __webpack_require__) {
eval("__webpack_require__.r(__webpack_exports__); __webpack_exports__['default'] = (function () { console.log('this is video')});");
})
}
window["webpackJsonp"].push([[0], module])
let modules = {
"./src/app3/index.js": function (module, exports, __webpack_require__) {
eval("document.getElementById('click').onclick = function () {" +
"__webpack_require__.e(0).then(__webpack_require__.bind(null, './src/app3/video.js')).then(r =>{ console.log(r.default()); })" +
"}");
}
};
webpack(modules);
function webpack(modules) {
// jsonp 回调函数
function webpackJsonpCallback(data) {
let chunkIds = data[0], moreModules = data[1], resolves = [];
chunkIds.forEach(id => {
if (chunksObj[id]) {
// chunksObj[id][0] 就是promise的resolve方法
resolves.push(chunksObj[id][0]);
}
chunksObj[id] = 0;
});
// add "moreModules" to the modules object,
Object.assign(modules, moreModules)
if (parentJsonpFunction) {
console.log(parentJsonpFunction);
console.log("yes")
parentJsonpFunction(data);
}
while (resolves.length) {
resolves.shift()();
}
};
// The module cache
let installedModules = {};
// object to store loaded and loading chunks
// undefined = chunk not loaded, null = chunk preloaded/prefetched
// Promise = chunk loading, 0 = chunk loaded
let chunksObj = {
"main": 0
};
// The require function
function __webpack_require__(moduleId) {
// Check if module is in cache
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// Create a new module (and put it into the cache)
let module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
// Execute the module function
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// Flag the module as loaded
module.l = true;
// Return the exports of the module
return module.exports;
}
// This file contains only the entry chunk.
// The chunk loading function for additional chunks
__webpack_require__.e = function requireEnsure(chunkId) {
let promises = [];
// JSONP chunk loading for javascript
let installedChunkData = chunksObj[chunkId];
if (installedChunkData !== 0) { // 0 means "already installed".
// a Promise means "currently loading".
if (installedChunkData) {
promises.push(installedChunkData[2]);
} else {
// setup Promise in chunk cache
let promise = new Promise(function (resolve, reject) {
installedChunkData = [resolve, reject];
chunksObj[chunkId] = installedChunkData;
});
promises.push(installedChunkData[2] = promise);
// start chunk loading
let script = document.createElement('script');
script.charset = 'utf-8';
script.timeout = 120;
script.src = __webpack_require__.p + "chunk-" + ({}[chunkId] || chunkId) + ".js"
// create error before stack unwound to get useful stacktrace later
let error = new Error();
function onScriptComplete(event) {
// avoid mem leaks in IE.
script.onerror = script.onload = null;
clearTimeout(timeout);
let chunk = chunksObj[chunkId];
if (chunk !== 0) {
if (chunk) {
let errorType = event && (event.type === 'load' ? 'missing' : event.type);
let realSrc = event && event.target && event.target.src;
error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')';
error.name = 'ChunkLoadError';
error.type = errorType;
error.request = realSrc;
chunk[1](error);
}
chunksObj[chunkId] = undefined;
}
}
// 超时之后,也是加载完成
let timeout = setTimeout(function () {
onScriptComplete({type: 'timeout', target: script});
}, 120000);
script.onerror = script.onload = onScriptComplete;
document.head.appendChild(script);
}
}
return Promise.all(promises);
};
// expose the modules object (__webpack_modules__)
__webpack_require__.m = modules;
// expose the module cache
__webpack_require__.c = installedModules;
// define getter function for harmony exports
__webpack_require__.d = function (exports, name, getter) {
if (!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, {enumerable: true, get: getter});
}
};
// define __esModule on exports
__webpack_require__.r = function (exports) {
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, {value: 'Module'});
}
Object.defineProperty(exports, '__esModule', {value: true});
};
// create a fake namespace object
// mode & 1: value is a module id, require it
// mode & 2: merge all properties of value into the ns
// mode & 4: return value when already ns object
// mode & 8|1: behave like require
__webpack_require__.t = function (value, mode) {
if (mode & 1) value = __webpack_require__(value);
if (mode & 8) return value;
if ((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
let ns = Object.create(null);
__webpack_require__.r(ns);
Object.defineProperty(ns, 'default', {enumerable: true, value: value});
if (mode & 2 && typeof value != 'string') for (let key in value) __webpack_require__.d(ns, key, function (key) {
return value[key];
}.bind(null, key));
return ns;
};
// getDefaultExport function for compatibility with non-harmony modules
__webpack_require__.n = function (module) {
let getter = module && module.__esModule ?
function getDefault() {
return module['default'];
} :
function getModuleExports() {
return module;
};
__webpack_require__.d(getter, 'a', getter);
return getter;
};
// Object.prototype.hasOwnProperty.call
__webpack_require__.o = function (object, property) {
return Object.prototype.hasOwnProperty.call(object, property);
};
// __webpack_public_path__
__webpack_require__.p = "";
// on error function for async loading
__webpack_require__.oe = function (err) {
console.error(err);
throw err;
};
let jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
let oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
jsonpArray.push = webpackJsonpCallback;
jsonpArray = jsonpArray.slice();
jsonpArray.forEach(e => webpackJsonpCallback(e))
let parentJsonpFunction = oldJsonpFunction;
// 加载入口文件
return __webpack_require__(__webpack_require__.s = "./src/app3/index.js");
}
AST 抽象语法树
- 语法检查,风格检查,错误检查,代码混淆压缩,代码转换
- 工具是 javascript parser实现 ,常见的有 esprima、traceur(google)、acorn、shift ,
- webpack用的是acorn ,babel是 @babel/parser
- 每个浏览器都是自己的抽象语法树标准,如谷歌的v8,火狐的spiderMonkey是业界标准,
let esprima = require("esprima");
// 遍历语法树
let estraverse = require("estraverse");
let code = "function d(){}";
let tree = esprima.parse(code);
estraverse.traverse(tree, {
enter(node){
console.log("in--", node.type)
},
leave(node){
console.log(node.type)
},
})
// 结果如下,进一次 出一次
enter-- Program
enter-- FunctionDeclaration
enter-- Identifier
leave-- Identifier
enter-- BlockStatement
leave-- BlockStatement
leave-- FunctionDeclaration
leave-- Program
引入大工具库的优化
就算开启了tree-shaking,
import _ from "lodash"
也不如import flatten from "lodash/flatten"
size 小
// mode dev 400k
// mode prod 73k
import _ from "lodash"
// mode dev 21k
// mode prod 3k
import flatten from "lodash/flatten"
const arr = [1, [2]];
console.log( _.flatten(arr))