Node.js 是 JavaScript 的服务器运行环境(runtime)。它对 ES6 的支持度更高。我们一般会使用Node来运行ES6代码。(现在的大部分主流浏览器已经支持ES6了)
我们可以直接使用如下命令来运行一个js文件:
node index.js
除了这些,我们也可以将ES6转化成ES5,这样就可以很好的兼容一些老的浏览器:
此时就需要用到ES6 转码器 babel
在学习ES6之前,我们可以学习一些npm,babel,polyfill, gulp,webpack之类的编译打包工具,和commonjs,seajs,requirejs等模块化规范,帮助我们更好的理解现下的模块化开发体系。
下面包含相关技术的简单解释:
npm 参考
我们已经知道了node是什么,而我们装node环境的时候,也会自动装好npm。npm全称:
Node Package Manager(Node包管理)学过Java的知道有个maven,.net 有个Nuget,python有个pip,都和npm差不多。
我们可以通过NPM尽情的管理我们的包,可以配置我们的package.json来添加依赖,可以通过npm命令来添加依赖。这样你想要bootstrap,ant-design,echart等三方库的时候只需要一行命令就可以解决。
npm config get prefix // 查看全局node_modues的目录
babel 参考
Babel 是一个 JavaScript 编译器
帮你把ES6语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境
Babel 默认只转换新的 JavaScript 句法(syntax),而不转换新的 API,比如
Iterator
、Generator
、Set
、Map
、Proxy
、Reflect
、Symbol
、Promise
等全局对象,以及一些定义在全局对象上的方法(比如Object.assign
)都不会转码。举例来说,ES6 在
Array
对象上新增了Array.from
方法。Babel 就不会转码这个方法。如果想让这个方法运行,可以使用core-js
和regenerator-runtime
(后者提供generator函数的转码),为当前环境提供一个垫片。下面列出的是 Babel 能为你做的事情:
- 语法转换
- 通过 Polyfill 方式在目标环境中添加缺失的特性 (通过引入第三方 polyfill 模块,例如 core-js)
- 源码转换(codemods)
// Babel 输入: ES2015 箭头函数 [1, 2, 3].map(n => n + 1);
// Babel 输出: ES5 语法实现的同等功能 [1, 2, 3].map(function(n) { return n + 1; });
babel使用插件的形式帮我们处理不同的特性,比如你可以安装
@babel/preset-react来编译react代码
@babel/preset-typescrip帮你编译typescript代码
安装完对应的包之后,我们需要在对应的babel配置文件中添加这些插件就可以了:
.babelrc 或 babel.config.json
presets: [ '@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript' ]
babel也支持自定义插件,利用 astexplorer.net 可以立即创建一个插件,或者使用 generator-babel-plugin 生成一个插件模板。
由于 Babel 支持 Source map,因此你可以轻松调试编译后的代码。
polyfill
polyfill就是我们常说的补丁代码,为了适应老版浏览器兼容性问题。我们必须给这些老版浏览器添加一些ES3~ES6的特性支持,于是引入了polyfill的概念。
这里面又涉及
@babel/preset-env
、@babel/polyfill
、@babel/transform-runtime
、@babel/runtime
以及core-js
总的来说,打补丁主要有三种方法:
- 手动打补丁
- 根据覆盖率自动打补丁
- 根据浏览器特性,动态打补丁
例如IE11不支持 Object.assign,可以进行如下手动打补丁,但是成本太大。
1. 可以使用三方库
Object.assign = require('object-assign')
2. 自己进行补丁
// Refer: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign if (typeof Object.assign != 'function') { // Must be writable: true, enumerable: false, configurable: true Object.defineProperty(Object, "assign", { value: function assign(target, varArgs) { // .length of function is 2 'use strict'; if (target == null) { // TypeError if undefined or null throw new TypeError('Cannot convert undefined or null to object'); } var to = Object(target); for (var index = 1; index < arguments.length; index++) { var nextSource = arguments[index]; if (nextSource != null) { // Skip over if undefined or null for (var nextKey in nextSource) { // Avoid bugs when hasOwnProperty is shadowed if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { to[nextKey] = nextSource[nextKey]; } } } } return to; }, writable: true, configurable: true }); }
在Webpack 的加持下,我们可以更现代化的方式打补丁:
1. @babel/preset-env
按需编译和按需打补丁是根据参数
targets
来确定目标环境,默认情况下它编译为ES2015,可以根据项目需求进行配置:...
presets: [
[
'@babel/preset-env',
{
// 支持chrome 58+ 及 IE 11+
targets: {
chrome: '58',
ie: '11',
}
},
],
]
...具体 targets 参数可参见 browserlist.
2. core-js
JavaScript 标准库core-js 是实现 JavaScript 标准运行库之一,它提供了从ES3~ES7+ 以及还处在提案阶段的 JavaScript 的实现。
3. @babel/plugin-transform-runtime
- 重利用 Babel helper 方法的babel插件@babel/plugin-transform-runtime 是对 Babel 编译过程中产生的 helper 方法进行重新利用(聚合),以达到减少打包体积的目的。此外还有个作用是为了避免全局补丁污染,对打包过的 bunler 提供"沙箱"式的补丁。
4.
@babel/polyfill
- core-js 和 regenerator-runtime 补丁的实现库
@babel/polyfill
通过定制polyfill
和regenerator
,提供了一个ES2015+ 环境polyfill
的库。因为它是由其他两个库实现的,直接引入其他两个库即可,所以已被废弃。// 实现 @babel/polyfill 等同效果
import 'core-js/stable'
import 'regenerator-runtime/runtime'
一般来说:应用的补丁 - 使用
@babel/preset-env
+useBuiltIns
useBuiltIns
告诉了@babel/preset-env
如何根据应用的兼容目标(targets)来处理polyfill
首先,在应用入口引入
core-js
:import 'core-js'
然后,配置useBuiltIns
参数为entry
,并指定core-js版本:{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "entry",
"corejs": 3,
"targets": {
"chrome": 58
} }
],
"@babel/preset-react"
]
}}
gulp 参考
gulp是一个工具包,可帮助您自动化开发工作流程中繁重而耗时的任务。
gulp旨在强调自动化前端构造流程,通过用户自定义配置一系列的任务(Task),并排列好顺序后执行,从而构建自动化流程。
gulp比较适合多页面应用开发,为通用website而生。webpack适合单页面应用开发,将资源模块化打包,适配各种模块系统,并且可以减少资源请求数量,从而减少应用程序必须等待的时间。
我们自定义的组件库,一般都会选择gulp来打包。
const gulp = require( 'gulp' ), babel = require( 'gulp-babel' ), uglify = require( 'gulp-uglify' ), minifyCss = require('gulp-clean-css'), webpack= require('vinyl-named'); /** 打包JS文件 */ async function compileESM() { return gulp .src(['packages/**/*.js']) .pipe(babel({ presets: ['@babel/preset-env'] })) .pipe(uglify()) .pipe(gulp.dest('lib')); } /** 打包css*/ async function compileCss() { return gulp .src(['packages/**/*.css']) .pipe(minifyCss()) .pipe(gulp.dest('lib')); } const build = gulp.parallel( compileESM, compileCss ); exports.build = build; exports.default = build;
webpack 参考
webpack 是一个模块打包器。webpack 的主要目标是将 JavaScript 文件打包在一起,打包后的文件用于在浏览器中使用,但它也能够胜任转换(transform)、打包(bundle)或包裹(package)任何资源(resource or asset)。
从而我们可以看出webpack侧重的是模块化前端开发流程,就像分类管理的概念,将相同东西(例如css文件,js文件,图片文件)分类组成成单独的模块。
creat-react-app本质上使用的就是webpack打包。
module.exports = { /** 入口 (多个文件作为一个入口 全局使用babel-polyfill polyfill将会被打包进这个入口文件中, 而且是放在文件最开始的地方)**/ entry: { app: ['babel-polyfill', './src/main.js'], }, publicPath: './', outputDir: 'dist', /** 打包时生成的生产环境构建文件的目录 **/ assetsDir: 'public', /** 放置生成的静态资源(s、css、img、fonts)的(相对于 outputDir 的)目录(默认'') **/ indexPath: 'index.html', /** 指定生成的 index.html 的输出路径(相对于 outputDir)也可以是一个绝对路径。 **/ lintOnSave: false, /** 是否在保存的时候Eslint检查 */ devServer: { port: 80, host: '0.0.0.0', open: true, index: '/index.html', overlay: { warnings: false, errors: false, }, proxy: {}, /** 代理 */ }, productionSourceMap: false, /** 禁止生成sourceMap文件 **/ chainWebpack: (config) => { /** ....配置使用loader */ }, };
CommonJS, AMD, CMD, UMD, ES6模块
CommonJS
2009年,美国程序员Ryan Dahl创造了Node.js 项目,将JavaScript语言用于服务器端编程。
这标志“JavaScript模块化编程”正式诞生。
Nodejs.的模块系统,就是参照CommonJS规范实现的。在CommonJS中,有个全局性方法require(),用于加载模块。
Common.JS的Modules规范实现了一套简单易用的模块系统,CommonJS对模块的定义也十分的简单。主要分为模块定义、模块引用及模块标识三个部分。因为commonJs加载是同步加载的,不适合浏览器端加载。需要注意点
1) require第一次加载某个模块时,Node会缓存该模块。以后再加载该模块,就直接从缓存取出该模块的module.exports属性。
2) CommonJS模块的加载机制是,输入的是被输出的值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
// a.js module.exports = { name: 'ysl', age: '27' } // b.js let obj = require('./a.js') console.log(obj) // { name: 'ysl', age: 27 }
RequireJS(AMD)
RequireJS是一个JavaScript模块加载器。它非常适合在浏览器中使用。
RequireJS是一个基于AMD规范实现的函数,它区别于传统的CommonJS的require规范。因为它能够异步地加载动态的依赖。
AMD ( Asynchronous Module Definition,译为异步模块定义)是一个在浏览器端模块化开发的规范。模块将被异步加载,模块加载不影响后面语句的运行。所有依赖某些模块的语句均放置在回调函数中。
AMD是Require. JS在推广过程中对模块定义的规范化的产出。// 先引入require.js <script src='../node_modules/requirejs/require.js' data-main='./index'></script> // moduleA.js define(function (){ var add = function (x,y){ return x+y; }; return { add: add }; }); // index.js require(['v', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){ // some code here // 可以在这里编写模块加载后的代码 }); // require()函数接受两个参数: // 第一个参数是一个数组,表示所依赖的模块['moduleA', 'moduleB', 'moduleC'] // 第二个参数是一个回调函数,当前面指定的模块都加载成功后,它将被调用 // 注意['moduleA', 'moduleB', 'moduleC']这里面的三个模块与index.js在同一个目录
UMD
严格上说,umd不能算是一种模块规范,因为它没有模块定义和调用,这是AMD和CommonJS(服务端模块化规范)的结合体,保证模块可以被amd和commonjs调用。
(function (root, factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define(['moduleA'], factory); } else if (typeof module === 'object' && module.exports) { // Node. Does not work with strict CommonJS, but // only CommonJS-like environments that support module.exports, // like Node. module.exports = factory(require('moduleA')); } else { // Browser globals (root is window) root.returnExports = factory(root.moduleA); } }(this, function (b) { //use b in some fashion. // Just return a value to define the module export. // This example returns an object, but the module // can return a function as the exported value. return {} }));
SeaJS(UMD)
Seajs追求简单、自然的代码书写和组织方式,具有以下核心特性:
简单友好的模块定义规范: Seajs 遵循CMD规范,可以像Node;js - -般书写模块代码。
自然直观的代码组织方式:依赖的自动加载、配置的简洁清晰,可以让我们更多地享受编码的乐趣。
Seajs还提供常用插件,非常有助于开发调试和性能优化,并具有丰富的可扩展接口。
CMD ( Common Module Definition,译为通用模块定义)规范明确了模块的基本书写格式和基本交互规则。该规范是在国内发展出来的。
CMD是SeaJS在推广过程中对模块定义的规范化的产出。// CMD define(function(require, exports, module) { var a = require('./a') a.doSomething() // 此处略去 100 行 var b = require('./b') // 依赖可以就近书写 b.doSomething() // ... })
ES6模块
JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。不再需要
UMD
模块格式了,将来服务器和浏览器都会支持 ES6 模块格式。
es6模块的语法分为两部分:export 模块导出、 import模块导入。// profile.js var firstName = 'Michael'; export { firstName }; // main.js import { firstName } from './profile.js';
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用
- CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
- CommonJS 模块的
require()
是同步加载模块,ES6 模块的import
命令是异步加载,有一个独立的模块依赖的解析阶段。