nej+regular环境使用es6的低成本方案
本文来自 网易云社区 。
希望在生产环境中使用es6/7,babel应该是最普遍的选择。这是babel官网中,它对自己的定义:
Babel 自带了一组 ES2015 语法转化器。这些转化器能让你现在就使用最新的 JavaScript 语法,而不用等待浏览器提供支持。
babel就像一个javascript文件预处理器,你可以自由使用es6/7语法,不用当心兼容性问题,因为浏览器中运行是babel为你处理妥帖的代码。为了方便使用,它提供了许多使用方法:webpack、gulp、browserify、grunt......
通过哪种方式来在当前技术栈(nej+regular+stateman)中使用babel,是一个值得深思熟虑问题,而没有经过深思熟虑就试图使用webpack的我,一度掉进了一个坑中:
webpack + babel 的踩坑过程
webpack应该是目前最流行的构建工具,关于它和babel的使用方法,网上的资料汗牛充栋,前人对各种可能发生的问题(比如ie8的兼容)基本都有了解决方法,换句话说就是这条路的坑比较少。
但是对我们而言,webpack+babel的方式存在以下几个问题:
- 改变打包方式带来的不确定性。贸然在生产环境中替换打包工具,风险太大。
- 大量的历史代码中使用的nej改良后的amd语法,webpack无法识别。webpack支持amd语法,也支持路径别名,但仅限于以别名开头的路径
//webpack.config.js
...
alias: {
pro: 'a/b/c'
}
...
//test.js
define(['pro/d']) //define(['a/b/c/d'])
define(['pro/e/{mode}/f']) //error
这个问题可以通过编写babel插件,根据nej的模块语法定制修改:babel-plugin-transform-nej-module
- ftl注入,多入口。这两个问题可以从考拉的一篇文章中找到答案。
除此之外,还遇到一个比较特殊的问题,webpack无法识别我们历史代码中的一个文件。
综上所述,如果有着丰富的webpack使用经验,能够承受改变打包方式带来的风险,可以考虑使用webpack来引入babel。
从webpack的踩坑过程中,找到了做es6/7改造的两个原则:
- 不改变历史代码
- 不改变打包方式
gulp + babel
根据上述的两个原则,,gulp无疑是个很好的选择:
- 当检测到文件是用nej的语法引入模块时,不处理; //不改变历史代码
- 当检测到文件是用es6语法引入模块时,把它改造成nej的语法,再进行es6/7的语法转换; // 保证nej可以识别,打包方式不变
因此,利用gulp来引入es6/7,过程应该是这样的:
虚线方框里是需要我们来做的工作:
- import/export转化为nej的模块语法
- 根据兼容需求,转化es6/7语法
通过babel的插件可以完成这两个任务:
import/export转化为nej的模块语法
babel本身会将import和export的语法转化为commonjs格式:
转化前 | 转化后 | 目标 |
---|---|---|
import a from 'A' |
var a = require('A') |
define('A', function(a){...} |
export {b} |
exports.b = b; |
{... return b} |
这个转化只是个简写,详细的转化后代码可以在这里看,代码解析可以参考这篇文章
目前没有前人的工作可以直接实现的我们的目标,所以必须自己编写一个,babel插件的编写可以参考这篇文章。简单的说,babel会把javascript代码解析为一棵语法树,通过修改这棵树来方便、准确的修改javascript代码。
插件babel-plugin-transform-es2015-modules-nej将es6的模块语法转换为nej的模块语法,主要流程为:
判断是否为nej模块->解析import,生成路径数组、文件名数组->解析export,生成return语句->将除了import和export的其它代码生成内容数组,并将return语句放入->利用这三个数组构建amd格式的模块语法
根据兼容需求,转化es6/7语法
babel通过.baberc
来配置
//.babelrc
{
preset: '...',
plugins: '...'
}
可以看到,babel的配置由preset
和plugins
构成,preset
是插件的集合,选择预设的插件集合配合一些解决比较特殊问题的插件,来完成babel的配置。
在npm中搜索babel-preset
和babel-plugin
能够获得3000+的结果,在预设的插件集合中,babel-preset-env
是非常省心的选择,它是一个动态的插件集合,通过指定你想要兼容的浏览器,它会帮你引入需要的插件。加上上一步中编写的transform-es2015-modules-nej
,配置就完成了。
babel转化后的代码会默认使用严格模式,如果历史代码中存在严格模式下报错的问题,记得在插件中加上transform-remove-strict-mode
{
"presets": [
["env", {
"targets": {
"browsers": ["last 2 versions", "IE 8-10"]
}
}]
],
"plugins": [
"transform-es2015-modules-nej"
]
}
API的转化
最后一个问题是:babel只转换语法,不转换api,所以需要polyfill来保证generate
、async
这些喜闻乐见的api的正常使用。polyfill.min.js
文件体积不算小:102kb,需要在每个页面中都引用,当然,加载一次过后,缓存可以帮助节省大部分时间。
最优解是在打包的时候,根据每个页面使用的api来引入对应的polyfill
。babel的官方插件babel-plugin-transform-runtime
,能做到只引入文件用到的api的polyfill,但是它的引入方式为commonjs。实际上,即使是amd方式,也难以和nej的模块语法完美融合。而且,针对文件来引用polyfill
仍然使得同一个页面引用多个相同polyfill
,加载重复数据。
因此,对于API的转化,由如下三种解决方案
- 每个页面引入
polyfill.min.js
; - 每个文件引入对应
polyfill
;(修改插件babel-plugin-transform-runtime
) - 打包时引入页面所需
polyfill
;(利用打包来polyfill)
最终方案
graph TD
A(gulp)-->|监视|B[raw/xxx/a.js]
B-->|发生改变|C(babel)
C-->|babel.rc|D(src/xxx/a.js)
A-->|sourcemap|E(src/xxx/a.js.map)
gulp检测文件的变动,通过babel转化es6代码,转化过程中,gulp生成对应文件的sourcemap:a.js.map
。
分为四步:
- 配置gulp
- 配置babelrc
- 配置gulp+babel生成sourcemap
- 引入polyfill
1. 配置gulp
安装gulp和babel
npm install --save-dev gulp;
npm install --save-dev gulp-babel
配置gulpfile.js:
const gulp = require('gulp');
const babel = require('gulp-babel');
gulp.task('babel', () =>
gulp.src('./raw/**/*.js')
.pipe(babel())
.pipe(gulp.dest('./src'))
);
gulp.task('watch:babel', () => {
gulp.watch('./raw/**/*.js', ['babel']);
});
2.配置babelrc
{
"presets": [
["env", {
"targets": {
"browsers": ["last 2 versions", "IE 8-10"]
}
}]
],
"plugins": [
"transform-remove-strict-mode","transform-es2015-modules-nej"
]
}
提醒不熟悉babel的小伙伴一句,这些插件和预设需要安装,babel包中并不提供:
npm install --save-dev babel-preset-env;
npm install --save-dev babel-plugin-transform-remove-strict-mode;
npm install --save-dev babel-plugin-transform-es2015-modules-nej;
3.配置gulp+babel生成sourcemap
修改gulpfile.js如下:
const gulp = require('gulp');
const babel = require('gulp-babel');
const sourcemaps = require('gulp-sourcemaps');
gulp.task('babel', () =>
gulp.src('./raw/**/*.js')
.pipe(sourcemaps.init())
.pipe(babel())
.pipe(sourcemaps.write('.',{sourceRoot: 'raw'}))
.pipe(gulp.dest('./src'))
);
gulp.task('watch:babel', () => {
gulp.watch('./raw/**/*.js', ['babel']);
});
生成sourcemap后,可以在浏览器中运行转换后代码,调试转换前代码。
4. polyfill
<script src="/res/vendorjs/core/polyfill.min.js"></script>
总结及效果
目前测试的情况,ie9及其以上环境有效,理论上支持ie8。
es6最大的优点是给码农带来的快乐,一个不是很明显的快乐对比如下:
原来这样写:
NEJ.define([
'text!./app.html',
'pro/cache/indexCache',
'pro/util/userUtil',
'pro/module/module',
'pro/util/util'
],function(template,
IndexCache,
userUtil,
Module,
util
){
var App = Module.extend({
template: template,
config: function(){
this.supr();
this.cache =new IndexCache();
util.extend(this.data, {
columns: [],
columnConfig: [{
isShowIntroPic: false,
isVertical: false
},{
isShowIntroPic: true,
isVertical: true
},{
isShowIntroPic: true,
isVertical: true
},{
isShowIntroPic: false,
isVertical: false
}],
courseUrlPrefix: userUtil.isUserLogin() ? this.$urlPrefix.termDetailPrefix : this.$urlPrefix.courseDetailPrefix
});
this.data.courseUrl = "/path/courses/";
},
init: function(){
this.supr();
this.getInitData();
},
enter: function(){
this.supr();
},
getInitData: function() {
this.cache.courseColumn( this.onGetCourseColumn._$bind(this));
},
onGetCourseColumn: function(data) {
for(var i=0; i<data.length; i++ ) {
var columnData = data[i];
var columnConfig = this.data.columnConfig[i];
var column = {
title: columnData.sectionName,
isShowIntroPic: columnConfig.isShowIntroPic,
isVertical: columnConfig.isVertical,
introPicSrc: columnData.photoUrl,
courseCards: []
};
var coursesData = columnData.termCardVos;
for(var j=0; j<coursesData.length; j++) {
var courseData = coursesData[j];
column.courseCards.push({
title: courseData.courseName,
url: this.data.courseUrlPrefix.replace(':termid', courseData.termId),
src: courseData.bigPhoto,
price: courseData.price == 0 ? '免费' : courseData.price + '元'
})
}
this.data.columns.push(column);
}
this.$update();
},
leave: function(){
this.supr();
}
});
return App;
});
现在可以这样写:
import template from './app.html';
import IndexCache from 'pro/cache/indexCache';
import userUtil from 'pro/util/userUtil';
import Module from 'pro/module/module';
import util from 'pro/util/util';
const App = Module.extend({
template: template,
config: function () {
this.supr();
this.cache = new IndexCache();
Object.assign(this.data, {
columns: [],
columnConfig: [{
isShowIntroPic: false,
isVertical: false
}, {
isShowIntroPic: true,
isVertical: true
}, {
isShowIntroPic: true,
isVertical: true
}, {
isShowIntroPic: false,
isVertical: false
}],
courseUrlPrefix: userUtil.isUserLogin() ? this.$urlPrefix.termDetailPrefix : this.$urlPrefix.courseDetailPrefix
});
this.data.courseUrl = '/path/courses/';
},
init: function () {
this.supr();
this.getInitData();
},
enter: function () {
this.supr();
},
getInitData: async function () {
const data = await this.cache.courseColumn();
for(let [columnIdx, columnData] of data.entries()) {
let columnConfig = this.data.columnConfig[columnIdx],
column = {
title: columnData.sectionName,
isShowIntroPic: columnConfig.isShowIntroPic,
isVertical: columnConfig.isVertical,
introPicSrc: columnData.photoUrl,
courseCards: []
},
coursesData = columnData.termCardVos;
for(let courseData of coursesData) {
column.courseCards.push({
title: courseData.courseName,
url: this.data.courseUrlPrefix.replace(':termid', courseData.termId),
src: courseData.bigPhoto,
price: courseData.price == 0 ? '免费' : `${courseData.price}元`
});
}
this.data.columns.push(column);
}
this.$update();
},
leave: function () {
this.supr();
}
});
export {
App
};
本文来自网易云社区,经作者曹阳授权发布。
更多网易研发、产品、运营经验分享请访问网易云社区。
nej+regular环境使用es6的低成本方案的更多相关文章
- 理解Docker(6):若干企业生产环境中的容器网络方案
本系列文章将介绍 Docker的相关知识: (1)Docker 安装及基本用法 (2)Docker 镜像 (3)Docker 容器的隔离性 - 使用 Linux namespace 隔离容器的运行环境 ...
- 将手机micro USB口转换为USB type C连接器的低成本方案
我们知道USB IF提出的type C连接器的终极目标是统一各种USB 接口. 尽管USB 3.0在PC市场上发展的风生水起,但是由于USB 3.0对手机4G LTE的EMI和RFI干扰,导致市场上除 ...
- 检测当前运行环境对es6的支持
摘自:http://es6.ruanyifeng.com/#docs/intro 1.查看 node 已经实现的 es6 特性 // Linux & Mac $ node --v8-optio ...
- Linux企业生产环境用户权限集中管理项目方案案例
企业生产环境用户权限集中管理项目方案案例: 1 问题现状 当前我们公司里服务器上百台,各个服务器上的管理人员很多(开发+运维+架构+DBA+产品+市场),在大家登录使用Linux服务器时,不同职能的员 ...
- MySQL分库分表环境下全局ID生成方案 转
在大型互联网应用中,随着用户数的增加,为了提高应用的性能,我们经常需要对数据库进行分库分表操作.在单表时代,我们可以完全依赖于数据库的自增ID来唯一标识一个用户或数据对象.但是当我们对数据库进行了分库 ...
- MySQL分库分表环境下全局ID生成方案
在大型互联网应用中,随着用户数的增加,为了提高应用的性能,我们经常需要对数据库进行分库分表操作.在单表时代,我们可以完全依赖于数据库的自增ID来唯一标识一个用户或数据对象.但是当我们对数据库进行了分库 ...
- 【转】MySQL分库分表环境下全局ID生成方案
转载一篇博客,里面有很多的知识和思想值得我们去思考. —————————————————————————————————————————————————————————————————————— 在大 ...
- nginx+php负载均衡集群环境中的session共享方案梳理
在网站使用nginx+php做负载均衡情况下,同一个IP访问同一个页面会被分配到不同的服务器上,如果session不同步的话,就会出现很多问题,比如说最常见的登录状态. 下面罗列几种nginx负载均衡 ...
- 【转】生产环境:Nginx高可用方案
准备工作: 192.168.16.128 192.168.16.129 两条虚拟机.安装好 Nginx 安装Nginx 更新 yum 源文件: rpm -ivh http://nginx.org/pa ...
随机推荐
- 【Django】uWSGI和Gunicorn【转】
因为nginx等优秀的开源项目,有不少本来不是做服务器的同学也可以写很多服务器端的程序了.但是在聊天中会发现,大家虽然写了不少代码,但是对wsgi是什么,gunicorn是什么,反向代理又是什么并不了 ...
- GitHub个人使用入门
今天突然想起来了github 于是开始了入门之旅 如果你用过svn 那么你用起来感觉入门比较快的(至少我是这么感觉的)和在svn服务器上建项目的流程很像 每次修改代码之后提交的过程是: add, co ...
- 查看MSSQL数据库每个表占用的空间大小
需要查看数据库表的大小,查询SQL Server联机从书得到如下语句: sp_spaceused 显示行数.保留的磁盘空间以及当前数据库中的表所使用的磁盘空间,或显示由整个数据库保留和使用的磁盘空间. ...
- kubernetes role
https://kubernetes.io/docs/admin/authorization/rbac/
- Fedora下安装Scrapy遇到的两个问题
error:/usr/lib/rpm/redhat/redhat-hardened-cc1: No such file or directory 需要安装redhat-rpm-config sudo ...
- C++ const引用
(1) 在实际的程序中,引用主要被用做函数的形式参数--通常将类对象传递给一个函数.引用必须初始化. 但是用对象的地址初始化引用是错误的,我们可以定义一个指针引用. 1 int ival ...
- Spark 性能相关参数配置详解-shuffle篇
随着Spark的逐渐成熟完善, 越来越多的可配置参数被添加到Spark中来, 在Spark的官方文档http://spark.apache.org/docs/latest/configuration. ...
- iOS单选和全选
在日常开发中单选.多选.全选经常遇到,所以写一个demo放上来供大家参考, 先看效果图: Demo地址:https://github.com/domanc/SingleAndAllSelect.git
- cocos2dx 屏幕分辨率问题
做手机上的软件首先要考虑的就是屏幕分辨率怎么解决.coco2dx已经有了很好的解决方法. 用cocos2dx的python脚本创建工程时默认生成一个Helloworld的demo.我们就以这个demo ...
- 无法查找或打开 PDB 文件解决办法
用VS调试程序时,有时会在VS底部的“输出”框中提示“无法查找或打开 PDB 文件”.这该怎么解决呢? 下面,我们以VS2013为例,来教大家解决办法. 工具/原料 VS 方法/步骤 打开VS20 ...