在本文中,我们将研究使用Babel和webpack创建一个用于处理现代JavaScript(在Web浏览器中运行)的构建设置。
这是确保特别是现代JavaScript代码与更大范围的浏览器兼容的必要条件。
像大多数与Web相关的技术一样,JavaScript一直在发展。 在过去的好日子里,我们可以将几个<script>
标记放入页面中,也许包括jQuery和几个插件,然后再使用。
但是,自从引入ES6以来,事情变得越来越复杂。 浏览器对较新的语言功能的支持通常是零散的,并且随着JavaScript应用程序变得更加雄心勃勃,开发人员开始使用模块来组织其代码。 反过来,这意味着如果您今天正在编写现代JavaScript,则需要在过程中引入一个构建步骤。
从下面的链接可以看到,从ES6向下转换为ES5可以大大增加我们可以支持的浏览器数量。
构建系统的目的是使为浏览器和生产准备好我们的代码所需的工作流程自动化。 这可能包括诸如将代码转换为不同标准,将Sass编译为CSS,捆绑文件,最小化和压缩代码等步骤。 为了确保这些步骤始终可重复,需要构建系统从单个命令以已知顺序启动步骤。
先决条件
为了进行后续操作,您需要同时安装Node.js和npm (它们打包在一起)。 我建议使用诸如nvm之类的版本管理器来管理Node的安装( 方法如下 ),如果您希望获得有关npm的帮助,请查看SitePoint的初学者友好型npm教程 。
设定
在计算机上的某个位置创建一个根文件夹,然后从终端/命令行导航到该文件夹。 这将是您的<ROOT>
文件夹。
使用以下命令创建一个package.json
文件:
npm init -y
注意: -y
标志使用默认设置创建文件,这意味着您不需要从命令行完成任何通常的详细信息。 如果需要,可以稍后在代码编辑器中对其进行更改。
在<ROOT>
文件夹中,将目录src
, src/js
和public
。 在src/js
文件夹会在哪里,我们会把我们的未经处理的源代码,并在public
文件夹将在transpiled代码将结束。
使用Babel进行转译
为了使自己前进,我们将安装babel-cli和babel-preset-env ,前者提供了将ES6转换为ES5的能力,后者使我们可以使用已编译的代码来定位特定的浏览器版本。
npm install babel-cli babel-preset-env --save-dev
现在,您应该在package.json
看到以下内容:
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-preset-env": "^1.6.1"
}
当我们在package.json
文件中时,让我们将scripts
部分更改为如下所示:
"scripts": {
"build": "babel src -d public"
},
这使我们能够通过脚本而不是每次直接从终端调用Babel。 如果您想了解有关npm脚本及其功能的更多信息, 请查看此SitePoint教程 。
最后,在测试Babel是否正在执行其操作之前,我们需要创建一个.babelrc
配置文件。 这是我们的babel-preset-env
包将为其转换参数所引用的。
在<ROOT>
目录中创建一个名为.babelrc
的新文件,并将以下内容粘贴到其中:
{
"presets": [
[
"env",
{
"targets": {
"browsers": ["last 2 versions", "safari >= 7"]
}
}
]
]
}
这会将Babel设置为可转换为每个浏览器的最后两个版本,以及v7或更高版本的Safari。 其他选项是否可用取决于您需要支持的浏览器。
保存该文件后,我们现在可以使用使用ES6的示例JavaScript文件对事物进行测试。 出于本文的目的,我修改了左键盘的副本以在许多地方使用ES6语法: 模板文字 , 箭头函数 , const和let 。
"use strict";
function leftPad(str, len, ch) {
const cache = [
"",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" "
];
str = str + "";
len = len - str.length;
if (len <= 0) return str;
if (!ch && ch !== 0) ch = " ";
ch = ch + "";
if (ch === " " && len < 10)
return () => {
cache[len] + str;
};
let pad = "";
while (true) {
if (len & 1) pad += ch;
len >>= 1;
if (len) ch += ch;
else break;
}
return `${pad}${str}`;
}
将其另存为src/js/leftpad.js
并从终端运行以下命令:
npm run build
如果一切如预期,在你的public
文件夹,你现在应该找到一个名为新文件js/leftpad.js
。 如果您打开它,将会发现它不再包含任何ES6语法,并且看起来像这样:
"use strict";
function leftPad(str, len, ch) {
var cache = ["", " ", " ", " ", " ", " ", " ", " ", " ", " "];
str = str + "";
len = len - str.length;
if (len <= 0) return str;
if (!ch && ch !== 0) ch = " ";
ch = ch + "";
if (ch === " " && len < 10) return function () {
cache[len] + str;
};
var pad = "";
while (true) {
if (len & 1) pad += ch;
len >>= 1;
if (len) ch += ch;else break;
}
return "" + pad + str;
}
使用ES6模块组织代码
ES6模块是一个JavaScript文件,其中包含您希望对其他JavaScript文件可用的函数,对象或原始值。 您从一个export
,然后import
另一个。 任何严肃的现代JavaScript项目都应考虑使用模块。 它们使您可以将代码分解为独立的单元,从而使事情更易于维护; 它们可帮助您避免命名空间污染; 它们有助于使您的代码更加可移植和可重用。
尽管大多数ES6语法已在现代浏览器中广泛使用,但模块还不是这种情况。 在撰写本文时,它们在Chrome,Safari(包括最新的iOS版本)和Edge中可用。 它们被隐藏在Firefox和Opera中的标志后面; 而且它们在IE11和大多数移动设备中都不可用(可能永远不会)。
在下一节中,我们将研究如何将模块集成到构建设置中。
出口
export关键字使我们可以使ES6模块可用于其他文件,并为此提供了两个选项-named和default。 使用命名的导出,每个模块可以有多个导出,而使用默认导出,每个模块只能有一个。 在需要导出多个值的地方,命名导出特别有用。 例如,您可能有一个模块,其中包含许多实用程序功能,这些功能需要在应用程序的各个位置提供。
因此,让我们将leftPad
文件转换为模块,然后可以在第二个文件中进行需求。
命名为出口
要创建命名导出,请将以下内容添加到leftPad
文件的底部:
export { leftPad };
我们也可以删除"use strict";
文件顶部的声明,因为默认情况下模块以严格模式运行。
出口倒数
由于在leftPad
文件中仅要导出一个函数,因此实际上可能是使用export default
的不错选择:
export default function leftPad(str, len, ch) {
...
}
同样,您可以删除"use strict";
文件顶部的声明。
进口
为了使用导出的模块,我们现在需要将它们导入到我们希望在其中使用的文件(模块)中。
对于export default
选项,可以使用您希望选择的任何名称导入导出的模块。 例如,可以像这样导入leftPad
模块:
import leftPad from './leftpad';
或者也可以将其导入为另一个名称,如下所示:
import pineapple_fritter from './leftpad';
从功能上来说,两者的工作原理完全相同,但是使用与导出时使用的名称相同的名称或使导入易于理解的名称显然很有意义-也许导出的名称将与另一个已存在的变量名称发生冲突。接收模块。
对于命名的导出选项,我们必须使用与其下导出模块相同的名称来导入模块。 对于示例模块,我们将以与export default
语法相同的方式导入它,但是在这种情况下,我们必须使用花括号将导入的名称包装起来:
import { leftPad } from './leftpad';
大括号对于命名导出是必需的,如果不使用大括号,它将失败。
如果需要,可以在导入时更改命名导出的名称,并且这样做,我们需要使用import [module] as [path]
语法稍微修改一下语法。 与export
,有多种方法可以执行此操作,所有方法都在MDN导入页面上进行了详细说明。
import { leftPad as pineapple_fritter } from './leftpad_es6';
同样,名称更改有点荒谬,但这说明了可以将它们更改为任何内容的意义。 除非您正在编写编写基于水果的食谱的例程,否则您应始终保持良好的命名习惯。
消耗导出的模块
为了利用导出的leftPad
模块,我在src/js
文件夹中创建了以下index.js
文件。 在这里,我遍历一个序列号数组,并在它们前面加上零,以使它们成为一个八个字符的字符串。 稍后,我们将利用这一点并将它们发布到HTML页面上的有序列表元素中。 请注意,此示例使用默认的导出语法:
import leftPad from './leftpad';
const serNos = [6934, 23111, 23114, 1001, 211161];
const strSNos = serNos.map(sn => leftPad(sn, 8, '0'));
console.log(strSNos);
像我们之前所做的那样,从<ROOT>
目录运行构建脚本:
npm run build
Babel现在将在public/js
目录中创建一个index.js
文件。 与我们的leftPad.js
文件一样,您应该看到Babel替换了所有ES6语法,而仅保留了ES5语法。 您可能还会注意到,它已将ES6模块语法转换为基于Node的module.exports
,这意味着我们可以从命令行运行它:
node public/js/index.js
// [ '00006934', '00023111', '00023114', '00001001', '00211161' ]
您的终端现在应该注销一个以零为前缀的字符串数组,以使它们全部为八个字符。 完成之后,该看一下webpack了。
介绍webpack并将其与Babel集成
如前所述,ES6模块允许JavaScript开发人员将其代码分解为可管理的块,但是其结果是必须将这些块提供给请求的浏览器,从而有可能向服务器添加数十个其他HTTP请求-我们确实应该避免。 这是webpack出现的地方。
webpack是一个模块捆绑器。 其主要目的是通过跟踪所有依赖项来处理您的应用程序,然后将它们全部打包为一个或多个可在浏览器中运行的捆绑包。 但是,取决于它的配置方式,它可能远远超过此。
webpack配置基于四个关键组件:
- 入口点
- 输出位置
- 装载机
- 外挂程式
入口:这是您应用程序的起点,Webpack可以从中确定其依赖项。
输出:这指定您希望将处理后的捆绑包保存在何处。
加载程序:这是一种将某事物转换为输入并生成其他事物作为输出的方法。 它们可以用来扩展webpack的功能,以处理不仅仅是JavaScript文件的内容,因此也可以将它们转换为有效的模块。
插件:这些插件用于将webpack的功能扩展到捆绑以外的其他任务,例如缩小,整理和优化。
要安装webpack,请在<ROOT>
目录中运行以下命令:
npm install webpack webpack-cli --save-dev
这会将webpack本地安装到项目中,并且还可以通过添加webpack-cli
从命令行运行webpack。 现在,您应该看到package.json
文件中列出了webpack。 在该文件中时,请按如下所示修改脚本部分,以使它现在知道直接使用webpack而不是Babel:
"scripts": {
"build": "webpack --config webpack.config.js"
},
如您所见,此脚本正在调用webpack.config.js
文件,因此让我们在<ROOT>
目录中使用以下内容创建该脚本:
const path = require("path");
module.exports = {
mode: 'development',
entry: "./src/js/index.js",
output: {
path: path.resolve(__dirname, "public"),
filename: "bundle.js"
}
};
这或多或少是webpack所需的最简单的配置文件。 您可以看到它使用了前面介绍的输入和输出部分(可以单独使用它们),但是还包含一个mode: 'development'
设置。
webpack可以选择使用“开发”或“生产” 模式 。 设置mode: 'development'
针对构建速度和调试进行优化,而mode: 'production'
针对运行时执行的速度和输出文件大小进行优化。 如果您想阅读更多有关如何在默认设置之外进行配置的信息,Tobias Koppers的文章“ webpack 4:模式和优化 ”中对模式进行了很好的解释。
接下来,从public/js
文件夹中删除所有文件。 然后重新运行:
npm run build
您会看到它现在包含一个./public/bundle.js
文件。 但是,打开新文件,我们开始的两个文件看起来就很不一样。 这是包含index.js
代码的文件部分。 即使它与我们的原始文件相比经过了很大的修改,您仍然可以选择其变量名:
/***/ "./src/js/index.js":
/*!*************************!*\
!*** ./src/js/index.js ***!
\*************************/
/*! no exports provided */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _leftpad__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./leftpad */ \"./src/js/leftpad.js\");\n\n\nconst serNos = [6934, 23111, 23114, 1001, 211161];\nconst strSNos = serNos.map(sn => Object(_leftpad__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(sn, 8, '0'));\nconsole.log(strSNos);\n\n\n//# sourceURL=webpack:///./src/js/index.js?");
/***/ }),
如果从<ROOT>
文件夹运行node public/js/bundle.js
,您将看到与以前相同的结果。
转码
如前所述, 加载程序使我们能够将一件事转换为另一件事。 在这种情况下,我们希望将ES6转换为ES5。 为此,我们还需要几个软件包:
npm install babel-loader babel-core --save-dev
要利用它们, webpack.config.js
需要在输出部分之后添加一个模块部分,如下所示:
module.exports = {
entry: "./src/js/index.js",
output: {
path: path.resolve(__dirname, "public/js"),
filename: "bundle.js"
},
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules)/,
use: {
loader: "babel-loader",
options: {
presets: ["babel-preset-env"]
}
}
}
]
}
};
这将使用regex语句来标识要通过babel-loader
进行转译的JavaScript文件,同时从中排除node_modules
文件夹中的任何内容。 最后,告诉babel-loader
使用较早安装的babel-preset-env
软件包,以建立.babelrc
文件中设置的转换参数。
完成后,您可以重新运行此命令:
npm run build
然后检查新的public/js/bundle.js
,您会发现ES6语法的所有痕迹都消失了,但是仍然产生与以前相同的输出。
将其带到浏览器
构建了可正常运行的webpack和Babel设置之后,是时候将我们所做的事情带到浏览器了。 需要一个小的HTML文件,该文件应在<ROOT>
文件夹中创建,如下所示:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Webpack & Babel Demonstration</title>
</head>
<body>
<main>
<h1>Parts List</h1>
<ol id="part-list"></ol>
</main>
<script src="./public/js/bundle.js" charset="utf-8"></script>
</body>
</html>
没有什么复杂的。 要注意的要点是<ol></ol>
元素(将在其中移动数字数组)和<script></script>
元素(位于结束</body>
标记之前),并链接回./public/js/bundle.js
文件。 到目前为止,一切都很好。
需要更多的JavaScript来显示列表,所以让我们更改./src/js/index.js
来实现:
import leftPad from './leftpad';
const serNos = [6934, 23111, 23114, 1001, 211161];
const partEl = document.getElementById('part-list');
const strList = serNos.reduce(
(acc, element) => acc += `<li>${leftPad(element, 8, '0')}</li>`, ''
);
partEl.innerHTML = strList;
现在,如果在浏览器中打开index.html
,应该会看到一个有序列表,如下所示:
更进一步
如上所述,我们的构建系统已经准备就绪。 现在,我们可以使用webpack捆绑我们的模块,并通过Babel将ES6代码转换为ES5。
但是,要移植我们的ES6代码,每次进行更改时都必须运行npm run build
,这有点麻烦。
添加“手表”
为了克服重复运行npm run build
的需要,您可以在文件上设置'watch'
,并在每次看到./src
文件夹中的文件之一发生更改时自动重新编译./src
。 要实现这一点,请修改package.json
文件的scripts
部分,如下所示:
"scripts": {
"watch": "webpack --watch",
"build": "webpack --config webpack.config.js"
},
要检查它是否正常运行,请在终端上运行npm run watch
,您将看到它不再返回命令提示符。 现在返回src/js/index.js
并将额外的值添加到serNos
数组中并保存它。 我的现在看起来像这样:
const serNos = [ 6934, 23111, 23114, 1001, 211161, 'abc'];
如果现在检查终端,您将看到它已注销,并且它已重新运行webpack build
任务。 然后返回浏览器并刷新时,您将看到已使用leftPad
处理的新值添加到列表的leftPad
。
自动刷新浏览器
现在,如果让Webpack每次进行更改都自动刷新浏览器,那将是非常不错的选择。 通过安装另一个名为webpack-dev-server
npm软件包webpack-dev-server
。 不过,不要忘记先执行Ctrl + c的watch
任务!
npm install webpack-dev-server --save-dev
完成之后,让我们向package.json
文件添加一个新脚本以调用新程序包。 scripts
部分现在应包含以下内容:
"scripts": {
"watch": "webpack --watch",
"start": "webpack --watch & webpack-dev-server --open-page 'webpack-dev-server'",
"build": "webpack --config webpack.config.js"
},
请注意--open-page
标志添加到脚本的末尾。 这告诉webpack-dev-server
使用其iframe模式在默认浏览器中打开特定页面。
现在运行npm start
,您应该看到在http://localhost:8080/webpack-dev-server/
上打开了一个新的浏览器选项卡,其中显示了部件列表。 要显示'watch'
功能正常,请转到src/js/index.js
并将另一个新值添加到serNos
数组的末尾。 保存更改后,您应该注意到它们几乎立即在浏览器中反映出来。
完成此操作后,剩下的唯一事情就是将webpack.config.js
的模式设置为production
。 设置好之后,webpack还将缩小输出到./public/js/bundle.js
的代码。 您应该注意,如果未设置mode
,则webpack将默认使用production
配置。
结论
在本文中,您已经了解了如何为现代JavaScript设置构建系统。 最初,它使用命令行中的Babel将ES6语法转换为ES5。 然后,您已经了解了如何使用带有export
和import
关键字的ES6模块,如何集成webpack来执行捆绑任务,以及如何在每次检测到对源文件的更改时添加监视任务以自动运行webpack。 最后,您已经了解了如何安装webpack-dev-server
以便在每次更改时自动刷新页面。
如果您希望进一步了解这一点,建议您阅读SitePoint对Webpack和模块捆绑的深入研究,并研究其他允许Webpack处理Sass和资产压缩任务的加载程序和插件。 也看eslint-loader
和插件更漂亮了。
快乐捆绑...