本文详细梳理Webpack中require的五种用法,包括CommonJS同步与异步、require.ensure预加载、require.include以及ES6 import。重点解析代码分片、懒执行与模块提取机制,并对比各语法在打包行为上的差异,推荐以CommonJS同步或ES6 import为主的最佳实践方案。
Webpack模块引入语法概览
在Webpack环境中,开发者可以使用CommonJS格式的同步require语法、AMD格式的require回调语法,以及Webpack特有的require.ensure和require.include,再加上ES6标准的import语法。面对这五种不同的模块引入方式,初学者容易感到困惑。本文将逐一解析这些语法的特点及其适用场景,帮助理清各自的区别。
CommonJS同步与异步加载
CommonJS同步语法
经典的CommonJS同步引入方式如下所示:
var a = require('./a');
a.show();
在这种模式下,Webpack会将引用的a.js模块直接打包进当前文件中。这是最基础且常见的打包情形,通常无需额外配置。
CommonJS异步加载
在CommonJS的Modules/Async/A规范中,定义了require.ensure语法。Webpack实现了这一规范,主要用于在构建阶段进行代码分片(Code Splitting),并实现异步加载。具体用法如下:
require.ensure([], function(require){
var list = require('./list');
list.show();
});
在上述用法中,我们给require.ensure的第一个参数传入了空数组。实际上,该参数可以接收模块名称列表,从而实现预加载但懒执行的效果。用法如下:
1.fb874860b35831bc96a8.js
这种自动生成的文件名可读性较差。如前文所述,可以通过给require.ensure传递第三个参数来指定文件名,示例如下:
require.ensure([], function(require){
var list = require('./list');
list.show();
}, 'list');
通过指定第三个参数,可以生成更具可读性的文件名:
list.fb874860b35831bc96a8.js
此外,还可以传入带有层级结构的字符串(如"question/list"),Webpack会据此自动创建对应的文件夹结构。
需要注意的是,如果在require.ensure的回调函数中引用了两个或更多模块,Webpack会将它们合并打包到一个文件中。例如:
require.ensure([], function(require){
var list = require('./list');
list.show();
var edit = require('./edit');
edit.display();
}, 'list_and_edit');
此时,list.js和edit.js将被合并打包为一个文件,命名为list_and_edit.js。如果希望保持模块独立,必须分别编写两个require.ensure来引用各自的文件。
这种要求在编码阶段就决定打包结果的做法,实际上违背了职责分离原则,我个人并不推荐。
预加载与懒执行机制
CommonJS预加载与懒执行
在上述用法中,向 require.ensure 传入空数组,此处可接收模块名以实现预加载懒执行。用法如下:
require.ensure(['./list'], function(require){
var list = require('./list');
list.show();
});
当给require.ensure的第一个参数传入['./list']时,浏览器会在执行到此处时下载list.js文件,但不会立即执行其中的代码(即Webpack官方所说的不进行evaluate)。真正的代码执行发生在后续调用var list = require('./list');时。这就是所谓的“懒执行”。
与之前类似,写在回调函数中的多个模块会被合并打包。此外,写在第一个参数数组中的模块也会与回调函数内的模块合并打包,无论是否手动执行。
这种写法混合了CommonJS和AMD的特性,且模块名称需要书写两次,显得不够优雅。为此,Webpack定义了一个专门的方法来实现预加载功能。
Webpack内置require.include
Webpack内置的require.include
require.include是Webpack提供的内置方法,没有对应的底层规范支持,因此属于辅助功能。它可以实现上述的预加载功能,且无需将模块名写在数组中,用法如下:
require.ensure([], function(require){
require.include('./list');//此处只加载不执行
});
根据Webpack官方文档,require.include还有一个作用:将子模块中的公共部分提取到父模块中。例如,如果child1和child2都引用了list.js,而在parent中通过require.include引入了list.js,那么子模块中的相关引用将被移除,相当于将模块提升到了父级。(这里的父子关系指的是引用层级)
官方文档对该方法的介绍较为简略,其实际用途有限。此外,require.include的返回值为undefined,因此若需使用模块,仍需通过常规方式引入:
require.ensure([], function(require){
require.include('./preview'); //加载
let p = require('./preview'); //执行
p.getUrl(); //使用
}, 'pre');
AMD异步加载语法
AMD异步加载
Webpack同时支持CommonJS和AMD规范,因此AMD的经典语法可以正常使用,例如:
require(['./list'], function(list){
list.show();
});
这样书写时,list.js同样会被单独打包成一个文件。与require.ensure类似,如果在回调中引用多个模块,它们会被合并打包,例如:
require(['./list', './edit'], function(list, edit){
list.show();
edit.display();
});
list.js和edit.js会被合并打包。不同的是,AMD语法不支持传入第三个参数来指定文件名,因此生成的文件名通常不可控且可读性较差。
ES6 import语法转换
ES6 import语法
在现代前端开发中,不使用ES6标准似乎难以融入主流。因此,代码中又增加了一种模块引入语法:import。需要注意的是,import最终会被转化为CommonJS或AMD格式,因此不应将其视为一种全新的模块引用机制。Babel默认会将ES6模块转换为CommonJS规范,因此无需额外转换为AMD。
因此,以下两种写法在功能上是等价的:
import list from './list';
//等价于
var list = require('./list');
尽管两者等价,但在项目中应只选择一种风格,避免混用导致代码混乱。
最佳实践与总结
总结
以上梳理了require的各种用法及其区别,开发者可根据项目需求进行选择。个人建议的最佳实践是向CommonJS方向靠拢,若希望尝试ES6风格,可使用import替代CommonJS的同步语法。
因此,代码中保持以下两种风格即可:
//可打包在一起的同步代码,使用import语法
import list from './list';
//需要独立打包、异步加载的代码,使用require.ensure
require.ensure([], function(require){
var list = require('./list');
});
显然,在编写代码时仍需对打包结果做出决策,这也是我不完全喜欢Webpack的原因。相比之下,Gulp等工具将编码与编译分离,更符合职责分离原则。不过,Webpack以模块为核心的打包方式具有其独特性。只要团队内部制定统一约定,即可避免混乱。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。