本文由Dan Prince和Ravi Kiran进行同行评审。 感谢所有SitePoint的同行评审人员使SitePoint内容达到最佳状态!
大多数人认为模块,依赖管理和动态加载是任何现代编程语言的基本要求-这些是2015年添加到JavaScript的一些最重要的功能。
模块在Node中被广泛使用,但我们的重点是如何在浏览器内部使用模块。 我们将探索一些历史,浏览当前危险的环境,最终目标是为当今最重要的JavaScript模块捆绑器(Browserify,Webpack和jspm)提供清晰的前进路径和赞赏。
最后,我们将研究如何将这些工具与诸如CoffeeScript,TypeScript和Babel之类的编译器一起使用。
历代模块
自1995年以来就存在JavaScript,并且到目前为止,还没有浏览器本机支持模块。 Node和CommonJS创建于2009年,npm中的绝大多数软件包都使用CommonJS模块。
Browserify于2011年发布,将CommonJS模块引入浏览器,允许客户端JavaScript require
npm软件包。 该工具将所有必需的依赖项捆绑到一个JavaScript文件中。
过去
诸如jQuery之类的库会将$
添加到全局范围或window
。
window.$ = function() { ... };
我们将脚本包含到库中,并使用它公开的全局对象。
<script src="jquery.js"></script>
<script>
$(function() { ... });
</script>
您自己的应用程序代码通常在诸如App
类的全局名称下命名,以防止污染全局范围。 没有它,只有很长时间您就会遇到名称冲突而事情崩溃的情况。
var App = {};
App.Models = {};
App.Models.Note = function() {};
未来
库以通用模块格式(ES6模块)导出对象。
export default function $() { ... }
我们将模块导入本地范围并使用它。
import $ from 'jquery';
$(function() { ... });
- 无需全局变量👍
- 源订单独立性
- 访问npm
- 无需命名空间自己的应用程序代码
- 根据需要随时动态加载模块
现在
这真的很复杂。 首先,有多种使用的模块格式:
捆绑资产的工具有多种形状和大小:
然后,您可能需要使用转译器:
- ES6的Babel
- CoffeeScript
- 打字稿
此外,还有各种库允许动态加载模块:
这些是当前正在使用的流行工具的简短列表-对于初学者和专家而言,这是一个雷区。 转换的成本也突出表明您可以混合使用许多这些工具并获得不同的结果。
让我们在2016年整合工具
前端开发人员使用构建工具已经很长时间了,但是直到最近几年,我们才看到构建步骤成为常态。 诸如Sass和CoffeeScript之类的工具帮助使预处理成为主流,但ES6的发展势头如今已吸引所有人。
JavaScript的社区在2015年做了一些很大的改进,但我们需要巩固的工具在2016年https://t.co/HGrLjiSQhb -尼古拉斯Bevacqua(@nzgb) 2016年1月8日,
我同意。
Gulp和Grunt在过去几年中非常流行,这些工具使您可以编写一系列转换来传递资产。 尽管很多人选择直接通过npm使用这些工具,但它们已经发挥了很大的作用,并且仍然很受欢迎-请参阅为什么我离开Gulp和Grunt 来使用npm 脚本和使用npm作为构建工具的指南 。
就我个人而言,我不再关心构建资产管道,我正在寻找的是最小的配置工具,这些工具可以让我根据需要使用现代工具:诸如Sass,Autoprefixer,Babel和Coffeescript之类的东西,适当的模块系统和加载器无需担心实施,配置和持续维护。 从本质上讲,在过去的几年中,每个开发人员都在投入时间来创建资产管道,这是在进行大量的车轮重新发明和大量的浪费时间。
该社区分为各种工具,例如Browserify,Webpack,jspm,Sprockets和Gulp。 这并不是一个真正的问题,只是让每个试图理解明确道路的人感到困惑。
明确起点
我们可以达成一些共识:
- ES2015模块是JavaScript真正的未来模块格式之一。
- Babel是当今首选的ES2015编译器。
- Telerik的一份关于JavaScript的未来的报告表明,本机加载器尚无法在浏览器中使用,鉴于模块加载障碍 ,完整的ES2015支持可能需要两年以上的时间。
- 如果您现在想使用模块,那么很可能会涉及到CommonJS。
让我们看看使用Browserify,Webpack和jspm进行的最小配置设置是什么样的,这些是当今最重要的JavaScript捆绑器。
一个新项目
mkdir modules-app
cd modules-app
npm init -y
npm install --save-dev browserify webpack jspm
mkdir src
touch src/{entry,lib}.js index.html
在您喜欢的文本编辑器中更新index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Modules!</title>
</head>
<body>
<script src="bundle.js"></script>
</body>
</html>
我们还需要一个服务器来运行代码-例如live-server ,它是一个很棒的零配置HTTP服务器,具有实时重载功能。 使用npm install -g live-server
全局安装它,然后从项目根目录运行live-server
以启动。
浏览器
Browserify通过捆绑所有依赖项,使您可以在浏览器中require('modules')
。
打开src/lib.js
并添加我们的第一个模块。
var double = function(number) {
return number * 2;
}
module.exports = {
double: double
}
打开src/entry.js
,我们将require
我们的模块并使用它。
var lib = require('./lib.js');
console.log(lib.double(2));
更新package.json
的scripts
部分
"scripts": {
"browserify": "browserify ./src/entry.js -o ./bundle.js"
},
使用npm run browserify
运行此脚本
Browserify将在项目根目录中创建bundle.js
,您应该在控制台中看到最多的输出4
。 要了解有关Browserify在做什么以及如何创建此捆绑包的更多信息,建议您在egghead.io上观看Browserify简介 。
恭喜你! 现在,浏览器中有了模块! 🎉
Browserify的另一个主要优点是,它不仅使您可以访问自己编写的模块,而且还可以访问npm模块,让我们安装lodash进行查看。
npm install lodash --save-dev
编辑src/lib.js
var sum = require('lodash/sum');
var double = function(number) {
return number * 2;
}
var addFive = function(number) {
return sum([number, 5]);
}
module.exports = {
double: double,
addFive: addFive
}
编辑src/entry.js
并调用我们的新addFive
函数
var lib = require('./lib.js');
console.log(lib.double(2));
console.log(lib.addFive(2));
使用npm run browserify
浏览器再次创建捆绑软件,在浏览器中,您将看到4
和7
,表明我们已成功导入并使用lodash的sum
函数。
如果到目前为止,您现在已经知道在浏览器中开始使用模块所需要的一切,这将带来我们一开始概述的许多好处。
- 无需全局变量👍
- 源订单独立性
- 访问npm
- 无需为自己的应用程序代码命名
稍后我们将在运行时查看模块的动态加载。
Webpack
Webpack是一个模块捆绑器。 Webpack接收具有依赖性的模块,并生成代表这些模块的静态资产。
让我们向package.json
添加一个新脚本来调用webpack
"webpack": "webpack ./src/entry.js bundle.js"
使用npm run webpack
运行它
Webpack将重写bundle.js
并且浏览器中的输出应该完全相同。
尝试运行npm run browserify
npm run webpack
和npm run webpack
并检查已编译的bundle.js
文件中的差异。 了解这些工具在内部的工作方式并不是很重要,需要注意的重要一点是,尽管实现方式不同,但它们实际上是在完成将CommonJS模块的相同代码编译为标准的对浏览器友好的JavaScript的相同任务。 每个模块都放在bundle.js
的函数中,并分配了一个ID,以便可以根据需要加载它。
Webpack的功能远不止于此! 这确实是模块捆绑器的瑞士军刀。 Webpack还提供了开箱即用的出色开发工具,例如热模块更换 ,该模块可在更改模块后自动在浏览器中自动重新加载各个模块,类似于LiveReload,但无需刷新页面。
对于不同资产类型css-loader
列表也越来越多,甚至具有css-loader
和style-loader
的css-loader
style-loader
也可以将CSS编译为JavaScript捆绑包,并在运行时将其注入页面。 这超出了本文的范围,但是在Webpack入门中可以找到更多内容 。
JavaScript编译器
这是当今使用的三种最受欢迎的编译器,您可能还想使用很长的编译成JS语言的 列表 。
在研究如何将其与模块捆绑器一起使用之前,让我们先看看如何直接使用工具。
npm install --save-dev coffee-script typescript babel-cli babel-preset-es2015
touch src/{coffee-lib.coffee,ts-lib.ts,es6-lib.js}
CoffeeScript
编辑coffee-lib.coffee
sum = require 'lodash/sum'
double = (number)-> number * 2
addFive = (number)-> sum([number, 5])
module.exports =
double: double
addFive: addFive
注意 :CoffeeScript对模块使用CommonJS语法
将脚本添加到package.json
以运行coffee
可执行文件
"coffee": "coffee --output ./dist ./src/coffee-lib.coffee"
用npm run coffee
运行它
打字稿
编辑ts-lib.ts
/// <reference path="lodash.d.ts" />
import * as _ from 'lodash';
const double = (value: number)=> value * 2
const addFive = (value: number)=> _.sum([value, 5])
export = {
double,
addFive
}
注意 :TypeScript具有自己的模块语法 ,看起来像ES2015模块语法和CommonJS的混合。
将脚本添加到package.json
以运行tsc
可执行文件
"tsc": "tsc --outDir ./dist ./src/ts-lib.ts"
用npm run tsc
运行它
编译器会抱怨无法找到lodash,因为它需要类型定义才能知道如何处理不是TypeScript文件的外部模块。 您可以通过以下方式获取定义文件:
cd src
curl -O https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/master/lodash/lodash.d.ts
cd ..
npm run tsc
巴别塔
编辑es6-lib.js
import sum from 'lodash/sum';
const double = (number)=> number * 2
const addFive = (number)=> sum([number, 5])
export {
double,
addFive
}
注意 :Babel了解可爱的新ES2015模块语法。
Babel需要一个配置文件来指定要使用的预设
echo '{ "presets": ["es2015"] }' > .babelrc
将脚本添加到package.json
以运行babel
cli
"babel": "babel ./src/es6-lib.js -o ./dist/es6-lib.js"
用npm run babel
运行它
/dist
的文件现在包含CommonJS模块格式的ES5代码,该代码可以与我们之前使用的Browserify或Webpack完美配合。 您可以先使用CommonJS将其转换为ES5,然后再进行捆绑,也可以使用其他软件包在一个步骤中完成这两个任务。
对于Browserify,有插件coffeeify , tsify和babelify可以转换和捆绑。
对于Webpack,有一些加载程序 ,例如coffee-loader , ts-loader和babel-loader ,需要使用不同语言的模块。
jspm
jspm是SystemJS通用模块加载器的软件包管理器,基于动态ES6模块加载器构建
jspm采用了不同的方法,并从模块加载器System.js开始 。 System.js是一个将在开发人员时遵循装入程序规范的项目。
安装并初始化jspm项目
npm install -g jspm
jspm init
接受所有默认值,并确保将Babel用作编译器,它将在System.js运行ES6样式模块时将其配置为使用Babel。
更新index.html
以加载和配置System.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Modules!</title>
</head>
<body>
<script src="jspm_packages/system.js"></script>
<script src="config.js"></script>
<!--<script src="bundle.js"></script>-->
<script>
System.import('src/entry.js');
</script>
</body>
</html>
在浏览器中,您会看到一些请求和lodash的404
,这是因为默认情况下jspm_packages
从jspm_packages
目录加载软件包。
运行jspm install lodash
安装在该目录中,您应该会在控制台中看到预期的输出,分别是4
和7
,这就是发生的情况:
- 我们的
entry.js
文件正在使用System.import('src/entry.js');
动态加载System.import('src/entry.js');
。 - System.js加载
entry.js
,发现它需要我们的lib
模块,因此可以在运行时获取它。 - System.js加载
lib.js
,看到它需要lodash/sum
并也获取了它。
System.js还知道如何直接与ES6一起使用,更新entry.js
以动态地需要我们的ES6模块并即时进行编译。
import lib from './es6-lib';
// import lib from '../dist/coffee-lib';
// import lib from '../dist/ts-lib';
console.log(lib.double(2));
console.log(lib.addFive(2));
您也可以通过一次取消注释这些行来尝试加载我们的CoffeeScript或TypeScript模块的ES5编译版本。 另一个选择是使用System.js插件来转换代码,而不需要预先编译的ES5代码。
将最终脚本添加到package.json
以使用jspm
创建捆绑包
"jspm": "jspm bundle src/entry bundle.js"
用npm run jspm
最后,取消注释index.html
bundle.js
的脚本标记,浏览器应加载可用于生产环境的捆绑包,而无需任何额外的http请求。
<script src="bundle.js"></script>
回顾Webpack
前面的Webpack示例是使用默认选项的最简单示例,它将带有CommonJS模块的entry.js
编译为一个捆绑包。 当使用Webpack做更多花哨的事情时,您将需要为所有加载程序配置创建一个自定义配置文件。
在项目的根目录中创建webpack.config.js
module.exports = {
context: __dirname + "/src",
entry: "./entry",
output: {
path: __dirname,
filename: "bundle.js"
},
module: {
loaders: [{
test: /\.js$/,
loader: 'babel-loader',
query: {
presets: ['es2015']
}
},{
test: /\.coffee$/,
loader: 'coffee-loader'
},{
test: /\.ts$/,
loader: 'ts-loader'
}]
}
}
更新index.html
以仅再次加载捆绑的文件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Modules!</title>
</head>
<body>
<script src="bundle.js"></script>
</body>
</html>
安装加载程序以使用Babel,CoffeeScript和TypeScript进行转译
npm install --save-dev babel-loader coffee-loader ts-loader
全局安装webpack
并在不带参数的情况下运行,以从我们的配置文件中创建捆绑包。
npm install -g webpack
webpack
现在,Webpack知道可以将这些加载程序用于这些文件扩展名,我们可以自由地使用entry.js
ES6,CoffeeScript或TypeScript,尝试将它们逐一取消注释。
import lib from './es6-lib.js';
// import lib from './coffee-lib.coffee';
// import lib from './ts-lib.ts';
Webpack的功能远远超过我在这里介绍的内容,但是这些简单的设置是一个很好的起点。
那里又回来
因此,我们结束了对模块的探索,它们确实解决了许多问题,并且确实可以减少应用程序的复杂性(如果工具不妨碍我们的工作)。 如果您尚未使用模块,现在是时候了。 无需花费不必要的时间来建立资产管道,而是使用Just Work™的简单工具。
Webpack是当前的主宰者,您可以对其进行配置以执行几乎所有操作。 jspm是满足您所有捆绑需求的出色工具,并且可以使用多种格式,并具有良好的开发人员体验。 Browserify仍然是现代模块捆绑器的祖父,是一个可靠的选择-它的生态系统已经发展为包括Webpack最受欢迎的一些功能(例如捆绑包拆分和热重装)。 最后,System.js非常适合需要在运行时加载额外模块的情况。
您不想在一个项目中使用上述所有工具,但重要的是要了解这三个流行的选项,以及在需要时如何使用转译器。 如果您只想使用模块,则具有默认选项的Browserify,jspm或Webpack将完成此工作。
保持工具简单和配置轻便。 快乐捆绑。
From: https://www.sitepoint.com/javascript-modules-bundling-transpiling/