webpack 文档(指南)- Shimming 预置依赖

Shimming 的意思是用垫隙片填入。shim 预设依赖就是说可以将第三方模块当作“垫隙片”填入我们的项目,所以预设依赖应该是全局可用的,甚至我们不需要手动声明引入这个模块(比如使用 requireimport 语句),而是直接使用它暴露在全局中的那个变量或常量就行。wepback 为我们提供了 ProvidePlugin 这个插件来实现这个方案。

当然,除了 ProvidePlugin 外,还有 imports-loaderexports-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.jsversion 作为成员导出了。

细粒度 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 中每个 modulethis 指向的是 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;
}

关于进一步优化,后面再单独拎出来

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值