JS模块化和babel转译工具

模块化

模块化的由来

JS主要在前端的浏览器中使用,js文件被下载缓存到客户端,在浏览器中执行。比如简单的表单本地验证,漂浮一个广告。

2005年之后,随着Google大量使用了AJAX技术之后,可以异步请求服务器端数据,带来了前端交互的巨大变化。前端功能需求越来越多,代码也越来也多。随着js文件的増多,众多文件通过<script>标签引入到当前页面中,这些JS文件没有约束,定义的变量在这个页面中相互覆盖,造成全局变量污染,2008年V8引攣擎发布,2009年诞生了Nodes,支持服务端JS编程。使用JS编程的项目规模越来越大,这就和大多后端语言一样,需要有效的模块化管理这些文件。由此诞生许多的解决方案。

  • CommonJS规范(2009年),使用全局 require函数导入模块,将所有对象约束在模块对象内部,使用exports导出指定的对象。最早这种规范是用在 Nodes后端的,后来又向前端开发移植,这样浏览器端开发也可以使用
  • AMD(Asyη chronous Module Definition)异步模块定义,这是由社区提出的一种浏览器端模块化标
    准。使用异步方式加载模块,模块的加载不影响它后面语句的执行。所有依赖这个模块的语句,都需要
    定义在一个回调函数,回调函数中使用模块的变量和函数,等模块加载完成之后,这个回调函数才会执
    行,就可以安全的使用模块的资源了。其实现就是 AMD/Require]s.AMD虽然是异步,但是会预先加载
    和执行。目前应用较少。
  • CMD( Common Module definition),使用seas,作者是淘宝前端玉伯,兼容并包解决了 Requirers的问题。CMD推崇 as lazy as possible,尽可能的懒加载JS文件。

由于社区的模块化呼声很高,ES6开始提供支持模块的语法,我们可以使用ES6中的语法export、import等语句进行模块的导入导出,但是目前运行环境包括V8引擎都不能很好的支持这种ES6新语法的模块导出方式,我们需要将采用ES6+新语法转换为ES5之前的语法才能运行。这就需要babel转译工具。

babel

将ES6+语法的JS文件转化为ES5语法规范的工具,以达到兼容运行环境的目的。

官网https://babeljs.io/
参考文档https://babel.docschina.org/docs/en/6.26.3/

这里使用6.x版本babel。

使用babel转译一个包

步骤
将我们的原js代码保存到一个src目录中(通常名为src)

1. 在src同级目录命令行执行  npm init  得到 package.json文件
2. 配置npm的镜像源:新建.npmrc文件写入  registry=https://registry.npm.taobao.org
3. 安装babel:命令行  npm install babel-cli --save-dev  得到 node_modules目录
4. 配置babel预设:创建 .babelrc 写入  {‘presets‘:[‘env‘]}
5. 安装配置的预设: npm install babel-preset-env --save-dev
6. 创建快捷命令: package.json文件中更改script为  
    script {
        ‘build‘:"babel -src -d lib"  
    }
7. 执行 npm run build  即可完成 
各步骤细节

1.使用初始化命令 npm init初始化这个目录后,该目录下将会创建一个package.json文件,并且在执行npm init 后将要求你输入对这个package.json的文件的初始配置,例如这个包名和版本号信息,git仓库,不输入则生成默认的内容。有了这个文件,npm可以通过这个package.json文件管理这整个包。在命令行中使用npm命令即可。

2.配置一个国内的镜像源,否则之后所有的包安装都会从国外官网中下载,包括babel包的下载安装,使用国内淘宝镜像将会提高速度。可以有三种

  • 配置到项目目录:本项目目录下创建 .npmrc文件写入,只在该项目生效
  • 配置到该用户家目录,本用户生效
  • 配置到npm安装目录,全局生效

3.下载并安装babel,执行npm install babel-cli --save-dev,会将babel-cli以及依赖的所有包下载到本地根目录中

  • --save 可以将这个安装的包极其版本号自动添加到package.json文件中去,作为本包的依赖包,
  • -dev 是指定该包在开发环境中使用的依赖,通常需要带着这两个属性。

当我们使用save-dev选项后,在package.json文件中将会增加依赖包名,添加后在安装我们自己包时,会自动安装这个依赖的包;

"devDependencies": {
	"babel-cli": "^6.26.0",
}

4.安装预设是为将来的转译设置转译规则,babel官方提供了以下转译引擎下载

babel-preset-env      当前环境支持的代码,新 target
babel-preset-es2015   ES2815转码规则
babel-preset-react    react转码规则

ES7不同阶段语法提案的转码规则(共有4个阶段),选装一个即可
babel-preset-stage-0
babel-preset-stage-1
babel-preset-stage-2
babel-preset-stage-3

通常安装babel-preset-env转码规则,执行npm install babel-preset-env --save-dev即可,安装完成后,为babel工具创建babelrc配置文件,写入{‘presets‘:[‘env‘]}内容,转译时将会根据这个配置选择转码规则。也可以配置其他已下载的转码规则使用,详见官网

5.babel包安装完成并配置预设后,就可以使用命令./node_modules/.bin/babel src --out-dir lib将我们src目录中源码执行转译写入lib文件中,lib文件就是转译的内容。

6.配置npm的快捷执行命令,在package.json文件中更改script, build为自定义的命令,后面为执行的真实命令,-d 是 --out-dir 的简写形式。

script {
        ‘build‘:"babel -src -d lib"  
    }

模块的导入导出

使用ES6标准中的导入导出语法在通常的环境和V8引擎的支持很不理想,通常不能正常执行,需要使用babel工具,将这些代码转化为旧版本的书写方式,才能执行。在ES6环境中的导入导出语法如下。

被导出的内容可以是任意的对象,包括变量,函数,类,对象均可。

被导出文件mod.js

export default function echo100 (){   // 函数作为默认导出,在导入文件中使用默认导出方式获得
    console.log(100)
}

let a = 20
let b = ‘b‘

class Car{
    constructor(name, price){
        this.name = name
        this.price = price
    }
    showcar(){console.log(`it‘s ${this.name}, need ¥${this.price}`)}
}

let newcar = new Car(‘polo‘, 130000)

export {newcar, a, b}    // 普通导出

普通导出使用 export + {} 指定需要导出变量名即可,使用export default语句,会将这个对象绑定到该模块对象的default属性上,且不用关心变量名,由导入方指定的变量来接收这个默认对象即可。

导入文件(test.js),导入上面mod.js文件中的内容

// import...from... 语法,指定导入文件变量名和路径即可

// 默认导入,Echo变量指向上面的默认导出 echo100对象
import Echo from ‘./mod.js‘  

// 普通导入,变量名必须一致,可重命名,类似对象解构
import {a as numx, b as strb, newcar} from ‘./mod.js‘

// 导入整个模块,并给模块对象重命名,mod变量代表整个 mod.js模块
import * as mod from ‘./mod.js‘

// 测试导入的内容

console.log(numx)
console.log(strb)
newcar.showcar()
Echo()

上面是Es6的语法标准,直接运行目前的环境是不支持的,使用babel将上面的进行转译。转译后的文件内容为

转译后的mod.js

‘use strict‘;

Object.defineProperty(exports, "__esModule", {
    value: true
});

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

exports.default = echo100;

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function echo100() {
    console.log(100);
}

var a = 20;
var b = ‘b‘;

var Car = function () {
    function Car(name, price) {
        _classCallCheck(this, Car);

        this.name = name;
        this.price = price;
    }

    _createClass(Car, [{
        key: ‘showcar‘,
        value: function showcar() {
            console.log(‘it\‘s ‘ + this.name + ‘, need \uFFE5‘ + this.price);
        }
    }]);
    return Car;
}();

var newcar = new Car(‘polo‘, 130000);

exports.newcar = newcar;
exports.a = a;
exports.b = b; 

转译后的test.js

‘use strict‘;

var _mod = require(‘./mod.js‘);
var mod = _interopRequireWildcard(_mod);

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }

console.log(_mod.a);

console.log(_mod.b);
_mod.newcar.showcar();
(0, mod.default)();

执行test.js文件,正常输出从mod中导入的内容。从mod包中导入的值不能对原值进行修改,是只读的,例如在 执行numx++操作,数组等复杂类型元素的替换等,引起原值的修改将会在转译时报语法错误。

JS模块化和babel转译工具

上一篇:Buuctf-web-[极客大挑战 2019]Upload


下一篇:超详细图解:从 0 搭建一个个人网站,也太简单了吧