一、使用新语言来开发项目
1、使用ES6语言
通常我们需要将采用ES6编写的代码转换成目前已经支持良好的ES5代码,包含如下:
- 将新的ES6语法用ES5实现,例如ES6的class语法用ES5的prototype实现;
- 为新的API注入polyfill,例如使用新的fetch API时在注入对应的polyfill后才能让低端浏览器正常运行。
-
认识Babel
Babel(https://babeljs.io) 可以方便地完成以上两件事。Babel是一个JavaScript编译器,能将ES6代码转为ES5代码转为ES5代码,让我们使用最新的语言特性而不用担心兼容性问题,并且可以通过插件机制根据需求灵活地扩展。在Babel执行编译的过程中,会从项目根目录下的 .babelrc 文件中读取配置。 .babelrc是一个JSON格式文件,内容大致如下:{ "plugins": [ [ "transform-runtime", { "polyfill": false } ] ], "presets": [ [ "es2015", { "modules": false } ], "stage-2", "react" ] }
1.1 Plugins
plugins属性告诉Babel要使用哪些插件,这些插件可以控制如何转换代码。
以上配置文件里的transform-runtime
对应的插件全名叫作babel-plugin-transform-runtime
,即在前面加上了babel-plufing-
。要让Babel正常运行,我们必须先安装这个插件:npm i -D babel-plugin-transform-runtime
babel-plufin-transform-runtime
是Babel官方提供的一个插件,作用是减少冗余的代码。Babel在将ES6代码转换成ES5代码时,通常需要一些由ES5编写的辅助函数来完成新语法的实现,例如转换class extent
语法时会在转换后的ES5代码里注入_extent
辅助函数用于实现继承:function _extent(target){ for(var i = 1; i < arguments.length; i++){ var source = arguments[i]; for(var key in source){ if(Object.prototype.hasOwnProperty.call(source, key)){ target[key] = source[key]; } } } return target; }
这个导致每个使用
class extent
语法的文件都被注入重复的_extent辅助函数代码,babel-plufin-transform-runtime
的作用在于将原本注入JavaScript文件里的辅助函数替换成一条导入语句:
var _extent = require('babel-runtime/helpers/_extent');
这样能减少Babel编译出来的代码的文件大小。
同时需要注意的是,由于babel-plugin-transform-runtime
注入了require('babel-runtime/helpers/_extent')
语句到编译后的代码里,需要安装babel-runtime
依赖到我们的项目后,代码才能正常运行。也就是说babel-plugin-tranform-runtime
和babel-runtime
需要配套使用,在使用babel-plugin-transform-runtime
后一定需要使用babel-runtime
。
1.2 Presets
presets属性告诉Babel要转换的源码使用了哪些新的语法特性,一个Presets对一组新语法的特性提供了支持,多个Presets可以叠加。Presets其实是一组Plugins的集合,每个Plugin完成一个新语法的转换工作。Presets是按照ECMAScript草案来组织的,通常可以分为三大类。
(1)已经被写入ECMAScript标准里的特性,由于之前每年都有新特性被加入到标准里,所以又可细分如下。
+ ES2015(https://babeljs.io/docs/plugins/preset-es2015/): 包含在2015年加入的新特性。
+ ES2016(https://babeljs.io/docs/plugins/preset-es2016/): 包含在2016年加入的新特性。
+ ES2017 (https://babeljs.io/docs/plugins/preset-es2017/): 包含在2017年加入的新特性。
+ Env(https://babeljs.io/docs/plugins/preset-env),包含当前所有ECMAScript标准里的最新特性。 -
接入Babel
在了解Babel后,下一步就需要知道如何在Webpack中使用它。由于Babel所做的事情是转换代码,所以应该通过Loader去接入Babel。Webpack的配置如下:
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: ['babel-loader'],
},
]
},
// 输出source-map以方便直接调试ES6源码
devtool: 'source-map'
}
以上配置命中了项目目录下的所有JavaScript文件,并通过babel-loader调用Babel完成转换工作。在重新执行构建前,需要先安装新引入的依赖:
# Webpack 接入Babel必须依赖的模块
npm i -D babel-core babel-loader
# 根据我们的需求选择不同的Plugins或Presets
npm i -D babel-preset-env
2. 使用TypeScript语言
- 认识TypeScript
TypeScript是JavaScript的一个超集,主要提供了类型检查系统和对ES6语法的支持,但不支持新的API。目前没有任何环境支持运行原生的TypeScript代码,必须通过构建将它转换成JavaScript代码后才能运行。
下面改造一下前面用过的例子,用TypeScript重写JavaScript。由于TypeScript是JavaScript的超集,直接将后缀.js改成.ts是可以的。但为了体现出TypeScript的不同,我们在这里重写JavaScript代码,并加入类型检查:
// show.ts
// 通过DOM元素,将content显示到网页上
// 通过ES6模块规范导出show函数
// 为show函数增加类型检查
export function show(content: string){
window.document.getElementById('app').innerText = 'Hello,' + content;
}
// main.ts
// 通过ES6模块规范导入show函数
import { show } from './show';
// 执行show函数
show('Webpack');
TypeScript官方提供了能将TypeScript转换成JavaScript的编译器。我们需要在当前项目的根目录下新建一个用于配置编译选项的tsconfig.json文件,编译器默认会读取和使用这个文件,配置文件的内容大致如下:
{
“compilerOptions”: {
"module": "commonjs", // 编译出的代码采用的模块规范
"target": "es5", // 编译出的代码采用ES的哪个版本
"sourceMap": true // 输出Source Map以方便调试
},
"exclude": [ // 不编译这些目录里的文件
"node_modules"
]
}
通过npm install -g typescript 安装编译器到全局后,可以通过tschello.ts命令编译出hello.js和hello.js.map文件。
- 减少代码冗余
TypeScript编译器会有与3.1节中Babel同样的问题:在将ES6语法转换成ES5语法时需要注入辅助函数。为了不让同样的辅助函数重复出现在多个文件中,可以开启TypeScript编译器的importHelpers选项,需要修改tsconfig.json文件如下:
{
"compilerOptions": {
"importHelpers": true
}
}
该选项的原理和Babel中介绍的babel-plugin-transform-runtime非常类似,会将辅助函数转换成如下导入语句:
var _tslib = require('tslib');
_tslib._extend(target);
这会导致编译出的代码依赖tslib这个迷你库,但避免了代码冗余。
- 集成Webpack
要让Webpack支持TypeScript,需要解决以下两个问题。
- 通过Loader将TypeScript转换成JavaScript。
- Webpack在寻找模块对应的文件时需要尝试ts后缀。
我们需要修改默认的resolve.extensions配置项。
相关的Webpack配置如下:
const path = require('path');
module.exports = {
// 执行入口文件
entry: './main',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist'),
},
resolve: {
// 先尝试以ts为后缀的TypeScript源码文件
extensions: ['.ts', '.js']
},
module: {
rules: [
{
test: /\.ts$/,
loader: 'awesome-typescript-loader'
}
]
},
devtool: 'source-map', // 输出Source Map以方便在浏览器里调试TypeScript代码
}
在运行构建前需要安装上面用到的依赖:
npm i -D typescript awesome-typescript-loader
安装成功后重新执行构建,我们将会在dist目录下看到输出的JavaScript文件bundle.js,以及对应的Source Map文件bundle.js.map。在浏览器里打开index.html页面后,可以在开发工具里看到和调试用TypeScript编写的源码。
3. 使用Flow检查器
- 认识Flow
Flow是Facebook开源的一个JavaScript静态类型检查器,它是JavaScript语言的超集。我们所需要做的就是在需要的地方加上类型检查,例如在两个由不同的人开发的模块对接的接口处加上静态类型检查,就能在编译阶段指出部分模块使用不当的问题。同时,Flow能通过类型推断检查出在JavaScript代码中潜在的Bug。
Flow的使用效果如下:
需要注意的是,该段代码的第一行//@flow告诉Flow检查器这个文件需要被检查。// @flow // 静态类型检查 function squarel(n: number): number { return n * n; } squarel('2'); // Error: squarel需要传入number作为参数 // 类型推断检查 function square2(n){ return n * n; // Error: 传入的string类型不能做乘法运算 } square2('2');
- 使用Flow
以上只是让我们了解Flow的功能,下面讲解如何运行Flow来检查代码。Flow检测器由高性能且跨平台的OCaml(http://ocaml.org)语言编写,它的可执行文件可以通过npm i -D flow-bin
安装,安装完成后可先配置Npm Script:
再通过"scripts": { "flow": "flow" }
npm run flow
去调用Flow执行代码检查。
除此之外,我们还可以通过npm i -g flow-bin
将Flow安装到全局,再直接通过flow命令执行代码检查。
安装成功后,在项目根目录下执行Flow, Flow会遍历出所有需要检查的文件并对其进行检查,输出错误结果到控制台,例如:
采用了Flow静态类型语法的JavaScript,是无法直接在目前已有的JavaScript引擎中运行的,要让代码可以运行,需要将这些静态类型的语法去掉。例如:Error: show.js:6 6: export function show(content){ ^^^^^^^ parameter `content`. Missing annotation Found 1 error }
有两种方式可以做到这一点。// 采用Flow的源代码 function foo(one: any, two: number, three?): string{} // 去掉静态类型语法后输出代码 function foo(one, two, three){}
- flow-remove-types (https://github.com/flowtype/flow-remove-types):可以单独使用,速度快。
- babel-preset-flow(https://babeljs.io/docs/plugins/preset-flow):与Babel集成。
- 集成Webpack
由于使用了Flow项目一般都会使用ES6语法,所以将Flow集成到使用Webpack构建的项目里的最方便方法是借助Babel。下面修改3.1节中的代码,为其加入Flow代码检查,改动如下。
(1)安装npm i -D babel-preset-flow
依赖到项目。
(2)修改.babelrc配置文件,加入Flow Preset:
"presets": [ ...[], "flow" ]
向源码里加入静态类型后重新构建项目,我们会发现采用了Flow的源码还是能在浏览器中正常运行的。
要明确构建的目的只是去除源码中的Flow静态类型语法,而代码检查和构建无关,许多编辑器已经整合了Flow,可以实时在代码中高亮显示Flow检查出的问题。
4. 使用SCSS语言
- 认识SCSS
SCSS(http://sass-lang.com)可以让我们用更灵活的方式写CSS。它是一种CSS预处理器,语法和CSS相似,但加入了变量、逻辑等编程元素,代码类似这样:
SCSS又叫作SASS,区别在于SASS语法类似于Ruby,而SCSS语法类似于CSS,熟悉CSS的前端工程师会更喜欢SCSS。$blue: #1875e7; div{ color: $blue; }
采用SCSS去写CSS的好处在于,可以方便的管理代码,抽离公共的部分,通过逻辑写出更灵活的代码。和SCSS类似的CSS预处理器还有LESS(http://lesscss.org)等。
使用SCSS可以提升编码的效率,但是必须将SCSS源代码编译成可以直接在浏览器环境下运行的CSS代码。SCSS官方提供了以多种语言实现的编译器,由于本书更倾向于前端工程师使用的技术栈,所以主要介绍node-sass(http://github.com/sass/node-sass)。
node-sass的核心模块是用C++编写的,再用Node.js封装了一层,以提供给其他Node.js调用。node-sass还支持通过命令进行调用,先将它安装到全局:
再执行编译命令:npm i -g node-sass
就能在源码同目录下看到编译后的main.css文件。# 将main.scss源文件编译成main.css node-sass main.scss main.css
- 接入Webpack
我们曾在1.4节介绍过将SCSS源代码转换成CSS代码的最佳方式是使用Loader,Webpack官方提供了对应的sass-loader(https://github.com/webpack-contrib/sass-loader)。
Webpack接入sass-loader的相关配置如下:
以上配置通过正则/.scss/匹配所有以.scss为后缀的SCSS文件,再分别使用3个Loader去处理。具体处理流程如下。module.exports = { module: { rules: [ { // 增加对SCSS文件的支持, test: /\.scss/, // SCSS文件的处理顺序为先sass-loader,再css-loader,再style-loader use: ['style-loader', 'css-loader', 'sass-loader'], }, ] } }
- 通过sass-loader将SCSS源码转换为CSS代码,再将CSS代码交给css-loader处理。
- css-loader会找出CSS代码中@import和url()这样的导入语句,告诉Webpack依赖这些资源。同时支持CSS Module、压缩CSS等功能。处理完后再将结果交给style-loader处理。
- style-loader会将CSS代码转换成字符串后,注入JavaScript代码中,通过JavaScript向DOM增加样式。如果我们想将CSS代码提取到一个单独的文件中,而不是和JavaScript混在一起,则可以使用在1.5节中介绍过的ExtractTextPlugin。
由于接入sass-loader,所以项目需要安装这些新的依赖:
# 安装Webpack Loader依赖 npm i -D sass-loader css-loader style-loader # sass-loader依赖node-sass npm i -D node-sass
本文为学习笔记:来源《深入浅出Webpack》