Shimming 的意思是用垫隙片填入。shim 预设依赖就是说可以将第三方模块当作“垫隙片”填入我们的项目,所以预设依赖应该是全局可用的,甚至我们不需要手动声明引入这个模块(比如使用 require 或 import 语句),而是直接使用它暴露在全局中的那个变量或常量就行。wepback 为我们提供了 ProvidePlugin 这个插件来实现这个方案。
当然,除了 ProvidePlugin 外,还有 imports-loader 和 exports-loader 这两个有意思的加载器,它们分别可以改变某个变量的指向,改变某个变量的导出。
预置全局变量
这里还是以 lodash 为例,通过全局的 “_” 来访问它。
webpack8-shim
|- index.html
|- index.js
|- package.json
|- webpack.config.js
|- /dist
|- /node_modules
index.js
function component() {
var element = document.createElement('div');
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
return element;
}
document.body.appendChild(component());
webpack.config.js
var HtmlWebpackPlugin = require('html-webpack-plugin');
var {CleanWebpackPlugin} = require('clean-webpack-plugin');
var webpack = require('webpack');
module.exports = {
mode: 'development',
entry: {
index: './index.js',
},
output: {
publicPath: '',
path: __dirname + '/dist',
filename: '[name].js'
},
module: {
rules: []
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({template: './index.html'}),
new webpack.ProvidePlugin({
_: "lodash"
})
]
}
编译打包后的代码片段如下:
"./index.js":
(function(module, exports, __webpack_require__) {
eval(`/* WEBPACK VAR INJECTION */
(function(_) {
function component() {\r\n
var element = document.createElement('div');\r\n
element.innerHTML = _.join(['Hello', 'webpack'], ' ');\r\n
return element;\r\n }\r\n
document.body.appendChild(component());\n
/* WEBPACK VAR INJECTION */
}.call(this, __webpack_require__(/*! lodash */ \"./node_modules/lodash/lodash.js\")))\n\n//# sourceURL=webpack:///./index.js?`);
})
可以发现,遇到 “_” 后, webpack 会将 lodash 的包引入进来,并提供给需要它的模块。
还可以使用 ProvidePlugin 插件将某个模块单独导出,通过配置一个路径数组,如 [module, child, …children?] 来实现这个功能。
index.js
function component() {
var element = document.createElement('div');
element.innerHTML = join(['Hello', 'webpack'], ' ');
return element;
}
document.body.appendChild(component());
webpack.config.js
var HtmlWebpackPlugin = require('html-webpack-plugin');
var {CleanWebpackPlugin} = require('clean-webpack-plugin');
var webpack = require('webpack');
module.exports = {
mode: 'production',
entry: {
index: './index.js',
},
output: {
publicPath: '',
path: __dirname + '/dist',
filename: '[name].js'
},
module: {
rules: []
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({template: './index.html'}),
new webpack.ProvidePlugin({
join: ['lodash', 'join']
})
]
}
编译打包后的代码片段如下:
"use strict";
eval(`
__webpack_require__.r(__webpack_exports__);\n
/* harmony import */
var lodash__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! lodash */ \"./node_modules/lodash/lodash.js\");\n
/* harmony import */
var lodash__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(lodash__WEBPACK_IMPORTED_MODULE_0__);\n\r\n
function component() {\r\n
var element = document.createElement('div');\r\n
element.innerHTML = Object(lodash__WEBPACK_IMPORTED_MODULE_0__[\"join\"])(['Hello', 'webpack'], ' ');\r\n
return element;\r\n
}\r\n
document.body.appendChild(component());\n\n
//# sourceURL=webpack:///./index.js?`);
})
这里 webpack 并没有将未使用的代码剔除,因为 lodash 并不是使用 ES6 Module 标准编写,而且它是具有“副作用的”,即影响了全局作用域。
全局 export
exports-loader 可以将全局变量作为一个普通的模块导出。
global.js
var version = '1.1.1';
var helpers = {
sum: (a, b) => a + b
};
index.js
import { version } from './global.js';
console.log(version);
webpack.config.js
var HtmlWebpackPlugin = require('html-webpack-plugin');
var {
CleanWebpackPlugin
} = require('clean-webpack-plugin');
var webpack = require('webpack');
module.exports = {
mode: 'development',
entry: {
index: './index.js',
},
output: {
publicPath: '',
path: __dirname + '/dist',
filename: '[name].js'
},
module: {
rules: [{
test: require.resolve('./global.js'),
loader: "exports-loader",
options: {
exports: ["version"],
},
}]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: './index.html'
})
]
}
查看编译结果:
"./global.js":
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval(`__webpack_require__.r(__webpack_exports__);\n
/* harmony export (binding) */
__webpack_require__.d(__webpack_exports__, \"version\", function() { return version; });\n
var version = '1.1.1';\r\n\r\n
/*** EXPORTS FROM exports-loader ***/\n\n\n\n//# sourceURL=webpack:///./global.js?`);
})
__webpack_require__.d = function(exports, name, getter) {
if(!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true, get: getter });
}
};
exports-loader 插件将 global.js 的 version 作为成员导出了。
细粒度 shim
一些模块的 this 指向的是 window,而当模块运行在 CommonJS的上下文中, this 指向的是 module.exports ,而有些时候我们需要将它变为指向 window 。
index.js
this.name = 'index';
function test() {
console.log(this.name);
}
webpack.config.js
var HtmlWebpackPlugin = require('html-webpack-plugin');
var {
CleanWebpackPlugin
} = require('clean-webpack-plugin');
var webpack = require('webpack');
module.exports = {
mode: 'development',
entry: {
index: './index.js',
},
output: {
publicPath: '',
path: __dirname + '/dist',
filename: '[name].js'
},
module: {
rules: [{
test: require.resolve('./index.js'),
loader: 'imports-loader',
options: {
wrapper: "window"
}
}]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: './index.html'
}),
new webpack.ProvidePlugin({
join: ['lodash', 'join']
})
]
}
打包编译后的结果。
"./index.js":
(function(module, exports) {
eval(`
/*** IMPORTS FROM imports-loader ***/\n\n
(function() {\n\r\n
this.name = 'index';\r\n
function test() { \r\n
console.log(this.name);\r\n
}\n
}.call(window));\n\n\n
//# sourceURL=webpack:///./index.js?`);
})
加载 Polyfills
加载 Polyfills 有多种方法,列如,想要引入 babel-polyfill ,我们只需要如下操作:
yarn add babel-polyfill -D
然后将其引入到我们的主 bundle 中。
import 'babel-polyfill';
this.name = 'index';
function test() {
console.log(this.name);
}
为了能够确保代码能够在低版本浏览器上正确地运行,Polyfill 必须运行于所有其他代码之前。有很多人说现代浏览器不需要 Polyfill ,这种说法不可靠,现代浏览器不可能实现了所有新的 JS 语法,所以文档中有一句话叫做:“修复损坏实现(repair broken implementation)” 。如果已经确保消除了这种顾虑,或者只使用了所有现代浏览器所支持的 JS 语法,那么就可以通过条件判断来决定是否要提前加载运行 Polyfill 以兼容旧版浏览器。
首先,安装 whatwg-fetch polyfill ,我们可以用它来获取一些数据,没有别的意思,这里只是用来获取测试数据用。其中,WHATWG(Web Hypertext Application Technology Working GroupWeb 超文本应用程序技术工作组)是一个负责维护与开发 Web 标准的社区,他们的工作成果包括 DOM、Fetch API,和 HTML。一些来自 Apple、Mozilla,和 Opera 的员工在 2004 年建立了 WHATWG。
yarn add whatwg-fetch -D
然后在 polyfill 中引入它。
polyfill.js
import 'babel-polyfill';
import 'whatwg-fetch';
index.js
function component() {
const element = document.createElement('div');
element.innerHTML = join(['Hello', 'webpack'], ' ');
// Assume we are in the context of `window`
this.alert("Hmmm, this probably isn't a great idea...");
return element;
}
document.body.appendChild(component());
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.json())
.then((json) => {
console.log(
"We retrieved some data! AND we're confident it will work on a variety of browser distributions."
);
console.log(json);
})
.catch((error) =>
console.error('Something went wrong when fetching this data: ', error)
);
webpack.config.js
var HtmlWebpackPlugin = require('html-webpack-plugin');
var {
CleanWebpackPlugin
} = require('clean-webpack-plugin');
var webpack = require('webpack');
module.exports = {
mode: 'development',
entry: {
index: './index.js',
polyfills: './polyfills.js'
},
output: {
publicPath: '',
path: __dirname + '/dist',
filename: '[name].js'
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: './index.html'
}),
new webpack.ProvidePlugin({
join: ['lodash', 'join']
})
]
}
index.html(删除了引入 polyfills 的脚本)
<!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>
</head>
<body>
<script>
// 如果浏览器支持 fetch 就不引入 polyfills
const modernBrowser = 'fetch' in window && 'assign' in Object;
if (!modernBrowser) {
const scriptElement = document.createElement('script');
scriptElement.async = false;
scriptElement.src = '/polyfills.bundle.js';
document.head.appendChild(scriptElement);
}
</script>
<script src="index.js"></script></body>
</html>
用 Live Server 打开构建后生成的 index.html (删除了引入 polyfills 的脚本)后,界面展示如下。
点击确定后,页面显示如下。
Hello webpack
控制台打印如下。
这里其实我有个疑问,为什么 this.alert 可以弹窗,在我的记忆里,webpack 中每个 module 的 this 指向的是 module.exports , 它就不会有 alert 这个方法。但是仔细一看,发现 index.js中使用的不是箭头函数。所以使用了全局的 this 。实在尴尬。。。
function component() {
const element = document.createElement('div');
element.innerHTML = join(['Hello', 'webpack'], ' ');
// Assume we are in the context of `window`
this.alert("Hmmm, this probably isn't a great idea...");
return element;
}