4

问题

使用 webpack 构建, 动态去加载 css 时,为什么使用 import() 会报错,使用 System.import() 可以?

$ webpack => 2.6.1

dynamic-load-css-file
dynamic-load-css-file-error


原因

require.ensure()

在 webpack1.x 中提供了 require.ensure() 方法动态导入文件,其函数签名如下:

require.ensure(dependencies, callback, chunkName)

简单示例:

require.ensure([], function(require) {
  const foo = require("./module");
});

注:require.ensure() 出自 CommonJS 中有一个 Modules/Async/A 规范,该规范定义了 require.ensure 语法。webpack 基于此基础,在打包的时候根据 require.ensure() 进行代码切分,并异步加载切分后的代码。

System.import()

ES6 Loader 规范定义了 System.import 方法,用于在运行时动态加载 ES6 模块,Webpack 把 System.import 作为拆分点;所以也可以通过 System.import() 方法实现动态引入,与 require.ensure()
不同的是 System.import() 返回的是一个 Promise,如:

System.import("./module")
  .then(module => console.log(module))
  .catch(err => console.log(Chunk loading failed))

import()

而在 webpack v2 (v2.1.0-beta.28) 废弃了 System.import() 方法,在 webpack v3 会完全删除System.import() 方法, 进而支持 ECMAScript State 3 草案阶段的原生 import() 方法来替代 System.import() 方法。

import("./module").then(module => {
    return module.default;
  }).catch(err => {
    console.log("Chunk loading failed");
  });

import

ES6 模块与 CommonJS 模块的差异 ,自行理解:

  • CommonJS

    • CommonJS 和 AMD 模块是运行时加载

    • CommonJS 模块就是对象,输入时必须查找对象属性

    • CommonJS 模块输出的是值的缓存,不存在动态更新

  • ES6 Module

    • ES6 模块是编译时加载

    • ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,再通过 import 命令输入

    • ES6 export 语句输出的接口,与其对应的值是动态绑定关系

由于 ES6 中的模块是编译时加载,import 命令会被 JavaScript 引擎静态分析,先于模块内的其他模块执行。所以,下面的代码会报错:

// 报错
if (x === 2) {
  import MyModual from './myModual';
}

注:在 Webpack 2(v2.2.0-rc.5) 中增加对 ES6 模块的原生支持。这意味着 Webpack 现在可以识别 import 和 export,不需要先把它们转换成 CommonJS 模块的格式。

require()、import、import()

require() 是 CommonJS 的实现,用于运行时加载,推荐阅读 require() 源码解读

import 是 ECMAScript 6 Module 的语法,import 是静态执行,推荐阅读 import 命令

import() 函数是 ECMAScript Stage 3 草案阶段的语法;用于完成动态加载即运行时加载,可以用在任何地方。import()函数 返回的是一个 Promise。类似于 CommonJs 的 require() ,区别主要是前者是异步加载,后者是同步加载,推荐阅读 proposal-dynamic-import

import() 的适用场景:

  • 按需加载

  • 条件加载

  • 动态的模块路径

结论

webpack 的 Dynamic Imports 实现主要是利用 ECMAScript的 import() 动态加载特性,而 import() 目前只是一个草案,如果需要用此方法,需要引入对应的转换器,如 babel-plugin-syntax-dynamic-import

模块化的实现对比

CommonJS AMD CMD ES6 Module
解决的问题 模块化开发 模块化开发 模块化开发 模块化开发
适用环境 服务端 浏览器 浏览器 浏览器/服务端
加载方式 同步加载/异步加载 异步加载 异步加载 同步加载/异步加载
模块定义 module.exports define(id?,dependencies?,factory) define(function(require,exports,module){ }) export
模块加载 require()/require.ensure() require([module], callback) require(module) import / import()
执行时机 提前执行 延迟执行
依赖原则 依赖前置 依赖就近
规范化的实现 require.js SeaJS
webpack 的支持 require.ensure() require() import / import()

参考

推荐阅读


chenbin92
829 声望41 粉丝

web前端开发