各种包管理器到ESLint,从CommonJS到AMD,再从ES6模块到Babel和Webpack,好多工具啊!
好累……
是的,本日我以为很疲倦。我不禁想,我本该当连续我的发卖职业,不应该抄近路做前端开拓。但我意识到,前端开拓是给年夜胆者准备的,而年夜胆者绝不会放弃,他们才是人生赢家。
以是我决定做人生赢家,我要写点东西给前端开拓和工具链疲倦的受害者们看。我要写一下我是若何将初学者级别的代码变成令人惊叹的产品级代码的,以及这个过程中我用到的工具。
现在开始吧!
我们在做的东西
实在没什么令人激动的东西。我们做了个Web运用,从某个API读取一些随机的用户,然后显示在前端上。它没有路由的能力。本文的终极目标是让你熟习前真个工具链。
我们的AngularJS代码中没有利用样板代码,以是我们不会被CLI的那些黑科技搞得晕头转向。把稳我用的是AngularJS,不是Angular。利用AngularJS的缘故原由是我找不到任何关于AngularJS工具链和打包的文章。
首先在根目录下建一个index文件。
<html>
<head>
<title>Random User!</title>
<link rel=\"大众stylesheet\"大众 href=\"大众https://unpkg.com/spectre.css/dist/spectre.min.css\"大众>
</head>
<body>
<div class=\"大众container\"大众>
<h1 class=\公众text-center\"大众>Random User!</h1>
</div>
<script src=\公众https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.0/angular.min.js\"大众></script>
</body>
</html>
中规中矩的老式代码。这段代码从CDN上加载AngularJS文件和一个最小化的CSS框架,然后开始一点点编写JavaScript代码并添加到index中。
但随着运用程序的增长,我们有必要跟踪所有的依赖(这里的依赖便是Angular)。
利用包管理器
许多人利用包管理器跟踪项目所需的依赖的版本。包管理器的最大卖点便是,它会访问依赖的GitHub主页,下载到你的文件夹里,并且跟踪下载的版本。这样可以担保,移动代码位置或者下载其他版本的依赖不会造成代码无法事情。
代码管理器有过duojs、jspm、bower、npm,现在还有Yarn。现在去装一个yarn,我们稍后会用到。
向运用程序里添加依赖时,yarn会下载所需的文件,并保存到node_modules文件夹中。之后,须要用到依赖时,可以在index文件里引入:
装好这个后,我们再往根目录下添加app.js、userController.js和userFactory.js文件,然后全都链接到index文件里。
/
/app.js
/
var app = angular.module(\"大众RandomApp\"大众, []);
// /userFactory.js
app.factory(\"大众UserF\公众, function($http) {
var UserF = {};
UserF.getUsers = function(){
return $http({
method: 'GET',
url: 'https://www.reqres.in/api/users',
})
};
return UserF;
});
// /userController.js
app.controller(\"大众userController\公众, function($scope, UserF){
$scope.users = [];
UserF.getUsers()
.then(function(res) {
$scope.users = res.data.data;
})
});
<!doctype html>
<html lang=\"大众en\"大众>
<head>
<meta charset=\公众UTF-8\"大众>
<meta name=\"大众viewport\"大众
content=\公众width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0\公众>
<meta http-equiv=\公众X-UA-Compatible\公众 content=\公众ie=edge\"大众>
<title>Random User!</title>
<link rel=\"大众stylesheet\"大众 href=\"大众https://unpkg.com/spectre.css/dist/spectre.min.css\"大众>
</head>
<body>
<div class=\公众container\"大众 ng-app=\"大众RandomApp\公众>
<h1 class=\"大众text-center\公众>Random User!</h1>
<div ng-controller=\公众userController\公众>
<div ng-repeat=\"大众user in users\"大众>
<div class=\公众card\"大众>
<div class=\公众card-image\"大众>
<img ng-src=\"大众{{user.avatar}}\公众 class=\公众img-responsive\公众>
</div>
<div class=\"大众card-header\"大众>
<div class=\公众card-title h5\"大众>{{user.first_name}} {{user.last_name}}</div>
</div>
</div>
</div>
</div>
</div>
<script src=\"大众node_modules/angular/angular.min.js\公众></script>
<script src=\"大众app.js\公众></script>
<script src=\"大众userController.js\公众></script>
<script src=\公众userFactory.js\公众></script>
</body>
</html>
index越来越大了。他碰着了他自己的问题。他的手心开始出汗,膝盖发软,胳膊也越来越沉重……
这种办法的问题
所有的script标签必须按照固定的顺序。app.js天生app,然后附加到全局的window工具上。这个app变量会被其他脚本文件用到。这种情形常日被称为“全局命名空间污染”。
如果你还在用这种办法,就趁早改了吧。它的问题是,不管我们什么时候打开哪个文件,都无法得知app变量的值究竟是什么。
这段代码的另一个语义上的问题是,它利用了匿名函数。匿名函数是JavaScript的天使,也是妖怪。
永久不要忘却给匿名函数起名字。这样往后调试代码会变得随意马虎很多。
那么,假如有个JS警察卖力找出这些问题,岂不是很好?
ESLint
ESLint是个清理器。就像个严格的结对编程的伙伴一样。清理器会在你运行运用程序之前就帮你调试代码,并且强制你和你的团队遵照整洁代码的实践。谁说这样的老师不存在的?
配置ESLint
我们利用Airbnb的样式配置,在我们编写代码时进行检讨,并指出不当的地方。上面的命令会将配置安装到node_modules目录下,但我们得见告ESLint怎么用这个配置。建立一个名为.eslintrc.json的文件,内容如下:
// .eslintrc.json
{
\"大众extends\"大众: [
\"大众airbnb/legacy\公众
],
\公众env\"大众: {
\公众browser\"大众: true
}
}
extends列表见告ESLint在它自己的默认规则之上利用Airbnb的规则。env变量见告ESLint不要抱怨没有初始化的window变量等。要清理所有文件,可以利用通配符 。
现在运行下ESLint看看有什么结果。
这些都是Airbnb样式指南定义的规则。我留给你自己去改正这些文件。一开始就有个清理器是最空想的。当然,你可以关闭某个特定的规则。
比如,如果你喜好不用分号,或者利用双引号而不是单引号,你可以关闭相应的规则。ESLint很灵巧,许可你这么做。
模块
现在来谈论下模块。在创建大规模运用时,我们哀求代码有良好的构造,以便于往后的扩展。
我们把代码放到不同的模块中,以实当代码分割的目的。JavaScript直到ES6才支持模块。但模块化的观点早在ES6之前就涌现了。
CommonJS
在ES6之前,人们利用CommonJS标准。你可以写一段代码,然后见告环境导出这段代码。之后就能利用像RequireJS之类的库导入模块了。
// util.js
module.export = {
noop: function(){},
validateUrl: function(s){
return s.matches(/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=])/)
}
};
// postController.js
var validateUrl = require('./util').validateUrl;
var handleSubmit = function handleSubmit(e) {
if(!validateUrl(e.target.value)) {
return;
}
submitUrl(e.target.value);
}
如果你玩过Node,你会以为这段代码看起来很眼熟。不过这个标准有个毛病——它是同步的。
也便是说,只有在validateUrl被require了之后,postController的第3行的handleSubmit才会被实行。在这之前代码会停息。
这种体系在Node.js中没什么问题。Node可以在做事器启动之前加载所有依赖,比如配置日志文件、连接云真个数据库、配置秘钥等。但在前端,这种方法并不是太空想。
异步模块定义(Asynchronous Module Definition,AMD)
顾名思义,这种办法会异步加载模块,比CommonJS的办法好一些。下面是利用AMD的代码(我加了几个函数)。看着眼熟吗?
define(['validateSpam', 'blockUser', function(validateSpam, blockUser){
return {
noop: function(){},
validateUrl: function(s) {
return s.matches(/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=])/)
},
validateSpammyComment: function validateSpammyComment(comment, userID) {
if(validateSpam(comment)) {
blockUser(userID);
return false;
}
return true;
}
}])
第1行看起来就像AngularJS中的依赖注入一样。
ES6模块
TC39委员会看到开拓者们利用外部库之后,他们深切感想熏染到JavaScript须要支持模块。因此他们在ES6中引入了模块功能。现在利用ES6吧!
function noop(){};
function validateUrl(s) {
return s.matches(/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=])/)
}
export {
noop,
validateUrl
}
import { validateUrl } from './util';
var handleSubmit = function handleSubmit(e) {
if(!validateUrl(e.target.value)) {
return;
}
submitUrl(e.target.value);
}
不须要再调用外部库。import和export都是原生支持的。但依然有些版本的浏览器并不能完备支持ES6的所有功能。
但这种浏览器之间的不一致并不能阻挡程序员编写下一代的JavaScript。像babel等工具可以扫描所有JavaScript,并将它们转译成能兼容所有浏览器的代码。因此,你的代码乃至可以支持IE之类的老浏览器。
Babel和ES6
好了,现在我们把旧的JavaScript转换成新的JavaScript。先做一点改动,以便添加模块功能。现在我们先不管清理器……让它去抱怨吧。
// /userFactory.js
let angular = window.angular;
let app = angular.module('RandomApp');
/
A User factory which gets the user list
@param $http
/
let userFactory = $http => {
let UserF = {};
UserF.getUsers = () => $http({
method: 'GET',
url: 'https://www.reqres.in/api/users'
});
return UserF;
};
app.factory('UserF', userFactory);
// /userController.js
let angular = window.angular;
let app = angular.module('RandomApp');
/
Controls the user
@param $scope
@param UserF
/
let userController = ($scope, UserF) => {
$scope.users = [];
UserF.getUsers().then(res => $scope.users = res.data.data);
};
userController.$inject = ['$scope', 'UserFactory'];
app.controller('userController', userController);
这段代码的问题
这段代码无法事情。由于ES6的let关键字创建的变量是代码块高下文内的变量,而在同一个高下文内无法重复定义代码块级别的变量。
别忘了:我们还在全局高下文里呢。现在来改正这个问题。
我希望重构代码的缘故原由是,我想引入babel,这样可以亲眼看看babel的邪术。现在可以在终端里安装babel:yarn add babel-cli babel-preset-env
这行命令会安装babel-cli和babel-preset-env。
babel插件和预设
JavaScript代码会通过一系列转换器,而我们可以选择须要什么转换器。你可以让它把箭头函数转换成匿名函数,转换扩展运算符(spread),转换for...of循环,等等。这些转换器叫做插件。
你可以选择任何你想要的插件。成组的插件叫做预设。babel-preset-env会给babel一个灵巧的目标。
它并不是指定某个特定版本的JavaScript,而是见告babel自动跟踪最新的n个版本浏览器。
现在来设置babel配置文件:.babelrc,把它放到根目录下。
{
\"大众presets\"大众: [
[\"大众env\"大众, {
\"大众targets\"大众: {
\公众browsers\"大众: \"大众last 2 versions\公众
}
}]
]
}
现在从终端运行如下命令,babel就能正常事情。输入以下命令:
node_modules/.bin/babel .js
很方便对吧?babel会在转换之前预览以下。
现在喘口气,考虑下我们学过的东西。我们将一个JavaScript文件分解成了许多个文件。我们添加了清理器,防止写出不合规范的代码。
我们利用未来的JavaScript语法,并将其转换成特定版本的浏览器能理解的东西。我们污染了全局命名空间,但暂时还能接管,我们稍后会办理这个问题。
如果有个工具能自动完成这统统就好了。我们给它所有代码,自动运行清理器找出所有缺点,然后转译成浏览器兼容的代码。没错,确实有这么个工具。现在把这些东西都自动化吧。
用Webpack进行构建
首先,把所有JS文件都移动到一个目录下。我们利用标准的命名办法,将文件夹命名为build。同时,我们重构下JavaScript文件,这样所有文件都能被构建到同一个文件下。
// /build/userController.js
/
Controls the user
@param $scope
@param UserF
/
let userController = ($scope, UserF) => {
$scope.users = [];
UserF.getUsers().then(res => $scope.users = res.data.data);
};
userController.$inject = ['$scope', 'userFactory'];
export default userController;
// /build/userFactory.js
/
A User factory which gets the user list
@param $http
/
let userFactory = $http => {
let UserF = {};
UserF.getUsers = () => $http({
method: 'GET',
url: 'https://www.reqres.in/api/users'
});
return UserF;
};
userFactory.$inject = ['$http'];
export default userFactory;
/// /build/app.js
import angular from 'angular';
import userController from './userController';
import userFactory from './userFactory';
angular.module('RandomApp', [])
.factory('userFactory', userFactory)
.controller('userController', userController);
现在创建webpack.config.js文件:
var path = require('path');
module.exports = {
mode: 'development', // tells webpack that this is a development build. the 'production' switch will minify the code among other things
devtool: 'cheap-eval-source-map', // generate source maps for better debugging and dont take much time.
context: __dirname, // since this runs in a node environment, webpack will need the current directory name
entry: './build/app.js', // take this file and add to the bundled file whatever this file imports
output: {
path: path.join(__dirname, 'dist'), // output this in a dist folder
filename: 'bundle.js' // and name it bundle.js
},
// read medium post to know what's module and devServer because I dont have much room for comments
module: {
rules: [{
enforce: 'pre',
loader: 'eslint-loader',
test: /\.js$/
}, {
loader: 'babel-loader',
test: /\.js$/
}]
},
devServer: {
publicPath: '/dist/',
filename: 'bundle.js',
historyApiFallback: true,
overlay: true
}
};
如果现在运行webpack,就会看到所有文件都被打包成一个文件,放到dist目录下:
webpack配置揭秘
祝贺你,给自己点褒奖吧。现在我们把所有文件都打包到一起,它险些可以用于生产环境了。
现在来谈论下这个配置。我会逐一先容每个配置项的意思。更详细的资料可以参考手册(https://webpack.js.org/)。
大多数配置项我都给出了注释。这里说说没加注释的那些。
Webpack加载器(module工具)
这个可以想像成一条流水线上的一串代码加载单元。末了一个加载器(这里是babel-loader)会最先被Webpack用来加载代码。
我们哀求webpack遍历所有代码,然后用babel-loader将代码转译成ES5。
加载器工具还须要一个test设置项。它用这个设置项来匹配所有它该当卖力的文件(本例中用一段正则表达式匹配了所有扩展名为.js的文件)。
转译之后,就实行下一个加载器(这里是eslint-loader)。末了,把所有改变从内存中写到文件中,然后输出到output工具指定的文件里。
但这并不是我们的配置文件的行为。我们在ESLint加载器上加了个enforce: pre,由于我们希望先运行清理器。
这是由于输出的结果只有一个文件,而且如果利用最小化和稠浊功能的话,这个文件常日会变成人类无法阅读的格式(生产环境中常常会如此)。
清理器看这个文件就会疯了。这不是我们想要的,以是我们希望webpack先运行清理器,再进行转译。
除此之外,你还可以利用好几个加载器,可以加载样式表文件、SVG图像,以及字体。有个我总会用到的加载器便是html-loader。
HTML加载器
在Angular下,我们常日会在directive/component中包含模板文件,因此可以在Webpack中利用html-loader进行加载。
templateUrl: './users/partial/user.tpl.html' // 把这种写法改成:templateUrl: require('./users/partial/user.tpl.html')
Webpack由一个超大规模的社区支持,他们写了很多精良的加载器,以及很完善的文档。不管你有什么需求,很可能已经有现成的加载器了。
Webpack开拓做事器(devServer)
Webpack开拓做事器是个独立于Webpack的模块。它会启动自己的做事器,然后监视任何文件的改动。如果文件发生变革,Webpack开拓做事器就会重新打包并自动刷新页面。
如果发生缺点,它会在屏幕上显示一个覆盖层(通过overlay配置项设置),并直接在浏览器中显示缺点信息。而且它速率非常快,由于统统都在内存中完成,不会访问硬盘。
当然,为了运行webpack开拓做事器,你首先得有一个根本的构建好的文件(即,至少要运行webpack一次以天生构建好的文件)。
一旦天生之后,就可以运行该命令。该命令会启动做事器并供应静态文件,打开浏览器(默认是8080端口),并持续监视任何变动。
搞定了!
不过这并不是结局。还有许多你能做的事情。在事情中,我们利用Flow在编码时进行静态类型检讨。
静态类型检讨器可以检讨代码,并在发生缺点时发出警告,比如调用函数时供应了缺点类型的参数等。Flow也可以集成到Webpack中。
我们还利用Prettier实现编码时的自动格式化。它能让代码更可读。
傻瓜都能写打算性能看懂的代码。
好的程序员写人类能看懂的代码。
—— Martin Fowler。
我要把这句话贴在我的桌子上。祝贺你!
你成功了!
如果你读完了这篇超长的文章,我要跟你击掌向你道喜,你是人生赢家。JavaScript对我而言并不随意马虎。
我很希望我在第一个项目中编写UI时能懂得这些东西。但我估计这便是前端开拓对我的意义。持续学习,持续进步。
原文:https://medium.freecodecamp.org/the-brain-fatigued-javascripters-guide-to-modern-frontend-tooling-in-2018-9818a04e9ec5
作者:Amin Mohamed Ajani,Javascript开拓职员,曾为FireFox开拓工具贡献代码。用户界面工程师,善于iQuanti,reactjs,nodejs和TheIsmaili。
译者:弯月,责编:胡巍巍
“征稿啦”
CSDN 公众年夜众号秉持着「与千万技能人共发展」理念,不仅以「极客头条」、「畅言」栏目在第一韶光以技能人的独特视角描述技能人关心的行业焦点事宜,更有「技能头条」专栏,深度解读行业内的热门技能与场景运用,让所有的开拓者紧跟技能潮流,保持警觉的技能嗅觉,对行业趋势、技能有更为全面的认知。
如果你有优质的文章,或是行业热点事宜、技能趋势的真知灼见,或是深度的运用实践、场景方案等的新见地,欢迎联系 CSDN 投稿,联系办法:微信(guorui_1118,请备注投稿+姓名+公司职位),邮箱(guorui@csdn.net)。