目录
- 前言
- babel 是什么
- babel 能做什么
- 工作流程
- 解析 Parse
- 转换 Transform
- 生成 Generator
- 使用方式
- babel-standalone
- 介绍
- 版本
- 示例
- 问答
- 补充
- cli
- babel-standalone
- 模块介绍
- 版本升级
- 问答
- 总结
前言
babel 是什么
Babel 是一个 JavaScript 编译器
这是babel 官网对 babel 一句短而精的定义, 该如何理解这句话的含义呢?首先定义它为一个编译器,其次只对 JavaScript 语言负责.
关于编译器概念可参考维基百科bk.tw.lvfukeji.com/wiki/%E7%BC…
babel 能做什么
这里我们只需要知道 babel 作为 JavaScript 编译器的工作流程:解析->转换->生成.
通俗的可以理解为 babel 负责把 JavaScript 高级语法、新增 API 转换为低规范以保障能够在低版本的环境下正常执行,接下来我们看 babel 是如何工作的.
工作流程
解析 Parse
万事开头难,第一步的解析工作该由谁完成?
babylon
Babylon 是一款 JavaScript 解析器.
babel 本身是不负责解析工作的,而是调用了 babylon.parse 方法对源码进行词法解析生成 AST 树.
转换 Transform
babel-traverse
babel-traverse
负责遍历 AST 树进行增删改的操作.
从第一步获取到 AST 树之后,调用 babel-traverse
库提供的 traverse 方法对树进行更新.
babel-types
一个基于 AST 树的工具库(可对节点增删改查校验).babel-traverse
对 AST 树进行操作的时候便使用了此库.
生成 Generator
最后一步将更新之后的 AST 树进行生成代码.
babel-generator
对外提供 generator 方法接收 ast 参数返回值为改变之后的源码.
以上则是对 babel 编译器整个流程大概的描述信息.所以 babel 是由一系列动作配合完成的.
使用方式
babel-standalone
介绍
由于 babel 是基于 node 环境下运行,对于非 node 环境(如浏览器),babel-standalone
这个开源项目提供了 babel.min.js
可通过<script>
方式引入使用.
题外话:babel-standalone已加入了babel
大家族(上岸成为有编制一员),以后下载的 7.x 版本 babel 包内可以看到它的身影.
版本
名称 | 版本 | 体积 | 备注 | 在线地址 | babel.js | 6.26.0 | 1.78MB | 未压缩 | unpkg.com/babel-stand… | babel.min.js | 6.26.0 | 773KB | 已压缩 | unpkg.com/babel-stand… | babel.js | 7.12.9 | 3.1MB | 未压缩 | unpkg.com/@babel/stan… | babel.min.js | 7.12.9 | 1.6MB | 已压缩 | unpkg.com/@babel/stan… |
---|
示例
- 示例一 es6 转码
- 使用
<script>
引入在线地址或者下载之后本地引用. - 将编写的 es6 代码放入
<script type="text/babel">
内,需要注意一点type
类型为text/babel
.
- 使用
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>babel-standalone es6 转码</title>
<script src="https://unpkg.com/@babel/standalone@7.12.9/babel.min.js"></script>
</head>
<body>
<div id="output"></div>
<script type="text/babel">
const getMsg = () => {
const name = 'Babel';
document.getElementById(
'output'
).innerHTML = `Hello ${name} version:${Babel.version}`;
};
getMsg();
</script>
</body>
</html>
-
示例二 模拟在线实时转码用户输入的脚本
-
这种方式适用于一些在线转码的场景:
-
babel 官网首页
-
babel-repl 在线转码
-
JSFiddle
-
JSBin
-
-
以上这些均引入babel.min.js
(可能引入的名称或版本不一样)通过调用Babel
对象提供的各种 API(如transform
、disableScriptTags
、transformScriptTags
...)实现在线实时转码.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>babel-standalone 模拟在线实时转码用户输入的脚本</title>
<script src="https://unpkg.com/@babel/standalone@7.12.9/babel.min.js"></script>
</head>
<body>
输入:
<textarea id="input" style="width: 100%" rows="15">
class UserInfo{
constructor(name='张三') {
this.name = name;
}
getUserName(){
return `${this.name}`;
}
}
const user=new UserInfo('张三');
console.log(user.getUserName());
</textarea>
实时转码:
<pre id="output"></pre>
<script>
var inputEl = document.getElementById('input');
var outputEl = document.getElementById('output');
function transform() {
try {
outputEl.innerHTML = Babel.transform(inputEl.value, {
presets: [
'es2015',
[
'stage-2',
{
decoratorsBeforeExport: false,
},
],
],
}).code;
} catch (e) {
outputEl.innerHTML = 'ERROR: ' + e.message;
}
}
inputEl.addEventListener('keyup', transform, false);
transform();
</script>
</body>
</html>
- 示例三 import、export 的使用
以上示例都是通过内嵌的方式在页面直接写 es6 代码,但实际开发中有可能需要以外链的方式引入脚本,所以我们看会遇到哪些问题.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>babel-standalone import、export 的使用</title>
<script src="https://unpkg.com/@babel/standalone@7.12.9/babel.min.js"></script>
</head>
<body>
<div id="output"></div>
<script type="text/babel" src="./index.js"></script>
</body>
</html>
// index.js
const getMsg = () => {
const name = 'Babel';
document.getElementById(
'output'
).innerHTML = `Hello ${name} version:${Babel.version}`;
};
getMsg();
把示例一从内嵌方式修改为外链引入脚本证明没问题.但我们不止编写一个 index.js 脚本,对于使用 import、export 这些 es6 语法是否也支持?
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>babel-standalone import、export 的使用</title>
<script src="https://unpkg.com/@babel/standalone@7.12.9/babel.min.js"></script>
</head>
<body>
<div id="output"></div>
<script type="text/babel">
export default {
name: '小朋友',
age: 18,
};
</script>
</body>
</html>
首先我们以内嵌方式运行之后抛错Uncaught ReferenceError: exports is not defined
,这里抛出exports
未定义而不是 es6 的export
未定义,由此说明内部是要把 es6 代码转换 CommonJS 模块规范(印证了前面介绍中提到过的由于 babel 是基于 node 环境下运行),而我们是需要在浏览器执行的,所以babel-standalone
项目提供的babel.min.js
包含了 babel 用到的所有插件(各种特性、规范),可以直接配置为 umd 模式即可.
6.x 版本配置data-plugins="transform-es2015-modules-umd"
7.x 版本配置data-plugins="transform-modules-umd"
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>babel-standalone import、export 的使用</title>
<script src="https://unpkg.com/@babel/standalone@7.12.9/babel.min.js"></script>
</head>
<body>
<div id="output"></div>
<script type="text/babel" data-plugins="transform-modules-umd">
export default {
name: '小朋友',
age: 18,
};
</script>
</body>
</html>
既然可以定义export
导出那该如何import
导入呢?这里babel-standalone
又给我们提供了data-module
定义导出的模块名称,然后导入即可.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>babel-standalone import、export 的使用</title>
<script src="https://unpkg.com/@babel/standalone@7.12.9/babel.min.js"></script>
</head>
<body>
<div id="output"></div>
<script
type="text/babel"
data-plugins="transform-modules-umd"
data-module="userInfo"
>
export default {
name: '小朋友',
age: 18,
};
</script>
<script type="text/babel" data-plugins="transform-modules-umd">
import userInfo from 'userInfo';
document.getElementById('output').innerHTML = `Hello ${userInfo.name}`;
</script>
</body>
</html>
这一些似乎都没发现问题,难道真的没有什么问题?那直接修改为外链的方式呢.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>babel-standalone import、export 的使用</title>
<script src="https://unpkg.com/@babel/standalone@7.12.9/babel.min.js"></script>
</head>
<body>
<div id="output"></div>
<script
type="text/babel"
data-plugins="transform-modules-umd"
src="./userInfo.js"
></script>
<script type="text/babel" data-plugins="transform-modules-umd">
import userInfo from 'userInfo';
document.getElementById('output').innerHTML = `Hello ${userInfo.name}`;
</script>
</body>
</html>
// userInfo.js
export default {
name: '小朋友',
age: 18,
};
如果仔细看上面这段代码的话,有没有发现script
标签内缺少了属性data-module
定义模块名称,依然可以正常执行呢?
划重点,babel.min.js
内部获取script
标签之后对属性做了判断,如果有src
属性则使用属性值作为模块名称(如src="./userInfo.js"
最终以userInfo
作为模块名称),如果没有src
属性则获取data-module
属性值作为模块名称,所以如果使用外链方式的话是不需要data-module
属性的(即使配置了也无效).
上面示例演示了 export
可以使用外链的方式,那import
又如何使用外链方式呢?
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>babel-standalone import、export 的使用</title>
<script src="https://unpkg.com/@babel/standalone@7.12.9/babel.min.js"></script>
</head>
<body>
<div id="output"></div>
<script
type="text/babel"
data-plugins="transform-modules-umd"
src="./userInfo.js"
></script>
<script
type="text/babel"
data-plugins="transform-modules-umd"
src="./index.js"
></script>
</body>
</html>
// userInfo.js
export default {
name: '小朋友',
age: 18,
};
// index.js
import userInfo from 'userInfo';
document.getElementById('output').innerHTML = `Hello ${userInfo.name}`;
import
的使用方式同export
的方式是一样的,这样就完成了以外链的方式引入脚本.
问答
说这个问题之前,大家是否考虑过如果没有外链引入<script src="./userInfo.js">
,只有<script src="./index.js">
会怎样?在index.js
内部已经通过import userInfo from 'userInfo';
导入,是否可以不需要外链的方式引入,如果import
导入多个 js 又会怎样?
稳住.所以拎到问答部分单独聊...
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>babel-standalone import、export 的使用</title>
<script src="https://unpkg.com/@babel/standalone@7.12.9/babel.min.js"></script>
</head>
<body>
<div id="output"></div>
<script
type="text/babel"
data-plugins="transform-modules-umd"
src="./index.js"
></script>
</body>
</html>
// index.js
import userInfo from 'userInfo';
document.getElementById('output').innerHTML = `Hello ${userInfo.name}`;
如上面的示例的,直接外链引入index.js
,抛出Uncaught TypeError: Cannot read property 'name' of undefined
错误,原因是userInfo
是undefined
所以userInfo.name
也无法取到值.
那我们就从转码开始入手.
// index.js 转码之后
(function (global, factory) {
if (typeof define === 'function' && define.amd) {
define(['userInfo'], factory);
} else if (typeof exports !== 'undefined') {
factory(require('userInfo'));
} else {
var mod = {
exports: {},
};
factory(global.userInfo);
global.index = mod.exports;
}
})(
typeof globalThis !== 'undefined'
? globalThis
: typeof self !== 'undefined'
? self
: this,
function (_userInfo) {
'use strict';
_userInfo = _interopRequireDefault(_userInfo);
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
document.getElementById('output').innerHTML = 'Hello '.concat(
_userInfo['default'].name
);
}
);
我们对比一下index.js
转码前后:
转码前 | 转码后 | import userInfo from 'userInfo'; | _userInfo = _interopRequireDefault(_userInfo); function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };} |
---|
我们看到转码之后的代码通过_interopRequireDefault(_userInfo)
函数对_userInfo
重新赋值的操作.(_interopRequireDefault
函数的作用判断对象是否需要添加default
属性).
划重点:这里插一段为什么会有__esModule
和default
属性,首先在本节开头提到过由于 babel 是基于 node 环境下运行,所以 babel 是要把 es6 的模块转换为 CommonJS 的形式,那么就需要了解两者的区别.
导出 | 导入 | es6 | export export default | import {} import | CommonJS | module.exports | require |
---|
通过对比发现 es6 的导出、导入可以是多种形式,而 CommonJS 则是单一的对象导出、导入.所以 babel 要把 es6 转换 CommonJS 的形式就需要一些辅助改动.
关于模块对象添加__esModule
属性,是为了标记此模块是否被转码,如果有此属性则直接调用模块(exports
)的default
属性导出对象(babel 会把 es6 的export default
默认导出对象转码为 exports.default
的形式,同时这种写法又符合 CommonJS 的规范module.exports = exports.default
),主要是做到 es6 转码后与 CommonJS 规范的一致性.
关于default
属性,上面介绍了是有__esModule
属性的情况下,如果没有__esModule
属性说明没有对该模块进行转换(有可能是一个第三方模块)对于这种情况直接调用模块(exports
)的default
属性会为undefined
,所以这种情况就直接返回一个对象并且该对象添加一个default
属性,把属性值指向自己(如上面这句转码之后的代码return obj && obj.__esModule ? obj : { default: obj }
)
快醒醒,这两个属性不是这里的重点,还记得问题是什么吗?userInfo
为什么是undefined
,看完转码之后的代码,我们只需要知道一点import
只是导入而已,至于导入的对象是否存在,是不属于转码的职责所在(转码不会检测导入的对象否存在...),还是继续查找userInfo
在哪里定义的吧...
继续看转码之后的代码发现在开头有if...else if...else
对各种环境(AMD、CommonJS、UMD)做判断,由于我们是浏览器内执行(UMD 模式),所以进入else
里在global.userInfo
全局对象上有个userInfo
,至此得出一个结论,在外链方式引入的脚本内直接使用import
导入对象引用,而外部无任何声明export
,此对象是undefined
(其实在 AMD 的define(['userInfo'], factory)
、CommonJS 的require('userInfo')
也是同理).
简单一句话:无论通过哪种形式引用,必须要声明,所以外链的方式还是需要script
声明.
简单一句话:无论通过哪种形式引用,必须要声明,所以外链的方式还是需要script
声明.
简单一句话:无论通过哪种形式引用,必须要声明,所以外链的方式还是需要script
声明.
此处使用的声明可能用词不太准确,但相信看到这里应该可以意会到.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>babel-standalone import、export 的使用</title>
<script src="https://unpkg.com/@babel/standalone@7.12.9/babel.min.js"></script>
</head>
<body>
<div id="output"></div>
<script
type="text/babel"
data-plugins="transform-modules-umd"
src="./userInfo.js"
></script>
<script
type="text/babel"
data-plugins="transform-modules-umd"
src="./index.js"
></script>
</body>
</html>
// userInfo.js
export default {
name: '小朋友',
age: 18,
};
// index.js
import userInfo from 'userInfo';
document.getElementById('output').innerHTML = `Hello ${userInfo.name}`;
// userInfo.js 转码后
(function (global, factory) {
if (typeof define === 'function' && define.amd) {
define(['exports'], factory);
} else if (typeof exports !== 'undefined') {
factory(exports);
} else {
var mod = {
exports: {},
};
factory(mod.exports);
global.userInfo = mod.exports;
}
})(
typeof globalThis !== 'undefined'
? globalThis
: typeof self !== 'undefined'
? self
: this,
function (_exports) {
'use strict';
Object.defineProperty(_exports, '__esModule', {
value: true,
});
_exports['default'] = void 0;
var _default = {
name: '小朋友',
age: 18,
};
_exports['default'] = _default;
}
);
// index.js 转码之后
(function (global, factory) {
if (typeof define === 'function' && define.amd) {
define(['userInfo'], factory);
} else if (typeof exports !== 'undefined') {
factory(require('userInfo'));
} else {
var mod = {
exports: {},
};
factory(global.userInfo);
global.index = mod.exports;
}
})(
typeof globalThis !== 'undefined'
? globalThis
: typeof self !== 'undefined'
? self
: this,
function (_userInfo) {
'use strict';
_userInfo = _interopRequireDefault(_userInfo);
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
document.getElementById('output').innerHTML = 'Hello '.concat(
_userInfo['default'].name
);
}
);
对于这个问题,请品一下,上面对__esModule
和default
属性的介绍,就是那段这两个属性不是这里的重点,现在是这个问题的答案.
其实和上面的示例是一样的,只需要外链多个script
即可.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>babel-standalone 如何使用多个import、export</title>
<script src="https://unpkg.com/@babel/standalone@7.12.9/babel.min.js"></script>
</head>
<body>
<div id="output"></div>
<script
type="text/babel"
data-plugins="transform-modules-umd"
src="./other.js"
></script>
<script
type="text/babel"
data-plugins="transform-modules-umd"
src="./userInfo.js"
></script>
<script
type="text/babel"
data-plugins="transform-modules-umd"
src="./index.js"
></script>
</body>
</html>
// other.js
export function random() {
return Math.floor(Math.random() * 10);
}
export const randomStr = '幸运数字:';
// userInfo.js
export default {
name: '小朋友',
age: 18,
};
// index.js
import { randomStr, random } from 'other';
import userInfo from 'userInfo';
function init() {
document.getElementById('output').innerHTML = `Hello ${
userInfo.name
} ${randomStr} ${random()}`;
}
init();
补充
对于开发者来说这种直接编写 es6 代码实时转码比较方便,同时也耗时影响性能,所以开发环境可以使用,对于生产环境还是推荐加载转码之后的脚本.
You are using the in-browser Babel transformer. Be sure to precompile your scripts for production
——官网建议.
未完待续.
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!