本文来自 网易云社区 。

希望在生产环境中使用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的方式存在以下几个问题:

  1. 改变打包方式带来的不确定性。贸然在生产环境中替换打包工具,风险太大。
  2. 大量的历史代码中使用的nej改良后的amd语法,webpack无法识别。webpack支持amd语法,也支持路径别名,但仅限于以别名开头的路径
  1. //webpack.config.js
  2. ...
  3. alias: {
  4. pro: 'a/b/c'
  5. }
  6. ...
  7. //test.js
  8. define(['pro/d']) //define(['a/b/c/d'])
  9. define(['pro/e/{mode}/f']) //error

这个问题可以通过编写babel插件,根据nej的模块语法定制修改:babel-plugin-transform-nej-module

  1. ftl注入,多入口。这两个问题可以从考拉的一篇文章中找到答案。

除此之外,还遇到一个比较特殊的问题,webpack无法识别我们历史代码中的一个文件。

综上所述,如果有着丰富的webpack使用经验,能够承受改变打包方式带来的风险,可以考虑使用webpack来引入babel。

从webpack的踩坑过程中,找到了做es6/7改造的两个原则:

  1. 不改变历史代码
  2. 不改变打包方式

gulp + babel

根据上述的两个原则,,gulp无疑是个很好的选择:

  1. 当检测到文件是用nej的语法引入模块时,不处理; //不改变历史代码
  2. 当检测到文件是用es6语法引入模块时,把它改造成nej的语法,再进行es6/7的语法转换; // 保证nej可以识别,打包方式不变

因此,利用gulp来引入es6/7,过程应该是这样的:

虚线方框里是需要我们来做的工作:

  1. import/export转化为nej的模块语法
  2. 根据兼容需求,转化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来配置

  1. //.babelrc
  2. {
  3. preset: '...',
  4. plugins: '...'
  5. }

可以看到,babel的配置由presetplugins构成,preset是插件的集合,选择预设的插件集合配合一些解决比较特殊问题的插件,来完成babel的配置。

在npm中搜索babel-presetbabel-plugin能够获得3000+的结果,在预设的插件集合中,babel-preset-env是非常省心的选择,它是一个动态的插件集合,通过指定你想要兼容的浏览器,它会帮你引入需要的插件。加上上一步中编写的transform-es2015-modules-nej,配置就完成了。

babel转化后的代码会默认使用严格模式,如果历史代码中存在严格模式下报错的问题,记得在插件中加上transform-remove-strict-mode

  1. {
  2. "presets": [
  3. ["env", {
  4. "targets": {
  5. "browsers": ["last 2 versions", "IE 8-10"]
  6. }
  7. }]
  8. ],
  9. "plugins": [
  10. "transform-es2015-modules-nej"
  11. ]
  12. }

API的转化

最后一个问题是:babel只转换语法,不转换api,所以需要polyfill来保证generateasync这些喜闻乐见的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)

最终方案

  1. graph TD
  2. A(gulp)-->|监视|B[raw/xxx/a.js]
  3. B-->|发生改变|C(babel)
  4. C-->|babel.rc|D(src/xxx/a.js)
  5. A-->|sourcemap|E(src/xxx/a.js.map)

gulp检测文件的变动,通过babel转化es6代码,转化过程中,gulp生成对应文件的sourcemap:a.js.map

分为四步:

  1. 配置gulp
  2. 配置babelrc
  3. 配置gulp+babel生成sourcemap
  4. 引入polyfill

1. 配置gulp

安装gulp和babel

  1. npm install --save-dev gulp;
  2. npm install --save-dev gulp-babel

配置gulpfile.js:

  1. const gulp = require('gulp');
  2. const babel = require('gulp-babel');
  3. gulp.task('babel', () =>
  4. gulp.src('./raw/**/*.js')
  5. .pipe(babel())
  6. .pipe(gulp.dest('./src'))
  7. );
  8. gulp.task('watch:babel', () => {
  9. gulp.watch('./raw/**/*.js', ['babel']);
  10. });

2.配置babelrc

  1. {
  2. "presets": [
  3. ["env", {
  4. "targets": {
  5. "browsers": ["last 2 versions", "IE 8-10"]
  6. }
  7. }]
  8. ],
  9. "plugins": [
  10. "transform-remove-strict-mode","transform-es2015-modules-nej"
  11. ]
  12. }

提醒不熟悉babel的小伙伴一句,这些插件和预设需要安装,babel包中并不提供:

  1. npm install --save-dev babel-preset-env;
  2. npm install --save-dev babel-plugin-transform-remove-strict-mode;
  3. npm install --save-dev babel-plugin-transform-es2015-modules-nej;

3.配置gulp+babel生成sourcemap

修改gulpfile.js如下:

  1. const gulp = require('gulp');
  2. const babel = require('gulp-babel');
  3. const sourcemaps = require('gulp-sourcemaps');
  4. gulp.task('babel', () =>
  5. gulp.src('./raw/**/*.js')
  6. .pipe(sourcemaps.init())
  7. .pipe(babel())
  8. .pipe(sourcemaps.write('.',{sourceRoot: 'raw'}))
  9. .pipe(gulp.dest('./src'))
  10. );
  11. gulp.task('watch:babel', () => {
  12. gulp.watch('./raw/**/*.js', ['babel']);
  13. });

生成sourcemap后,可以在浏览器中运行转换后代码,调试转换前代码。

4. polyfill

  1. <script src="/res/vendorjs/core/polyfill.min.js"></script>

总结及效果

目前测试的情况,ie9及其以上环境有效,理论上支持ie8。

es6最大的优点是给码农带来的快乐,一个不是很明显的快乐对比如下:

原来这样写:

  1. NEJ.define([
  2. 'text!./app.html',
  3. 'pro/cache/indexCache',
  4. 'pro/util/userUtil',
  5. 'pro/module/module',
  6. 'pro/util/util'
  7. ],function(template,
  8. IndexCache,
  9. userUtil,
  10. Module,
  11. util
  12. ){
  13. var App = Module.extend({
  14. template: template,
  15. config: function(){
  16. this.supr();
  17. this.cache =new IndexCache();
  18. util.extend(this.data, {
  19. columns: [],
  20. columnConfig: [{
  21. isShowIntroPic: false,
  22. isVertical: false
  23. },{
  24. isShowIntroPic: true,
  25. isVertical: true
  26. },{
  27. isShowIntroPic: true,
  28. isVertical: true
  29. },{
  30. isShowIntroPic: false,
  31. isVertical: false
  32. }],
  33. courseUrlPrefix: userUtil.isUserLogin() ? this.$urlPrefix.termDetailPrefix : this.$urlPrefix.courseDetailPrefix
  34. });
  35. this.data.courseUrl = "/path/courses/";
  36. },
  37. init: function(){
  38. this.supr();
  39. this.getInitData();
  40. },
  41. enter: function(){
  42. this.supr();
  43. },
  44. getInitData: function() {
  45. this.cache.courseColumn( this.onGetCourseColumn._$bind(this));
  46. },
  47. onGetCourseColumn: function(data) {
  48. for(var i=0; i<data.length; i++ ) {
  49. var columnData = data[i];
  50. var columnConfig = this.data.columnConfig[i];
  51. var column = {
  52. title: columnData.sectionName,
  53. isShowIntroPic: columnConfig.isShowIntroPic,
  54. isVertical: columnConfig.isVertical,
  55. introPicSrc: columnData.photoUrl,
  56. courseCards: []
  57. };
  58. var coursesData = columnData.termCardVos;
  59. for(var j=0; j<coursesData.length; j++) {
  60. var courseData = coursesData[j];
  61. column.courseCards.push({
  62. title: courseData.courseName,
  63. url: this.data.courseUrlPrefix.replace(':termid', courseData.termId),
  64. src: courseData.bigPhoto,
  65. price: courseData.price == 0 ? '免费' : courseData.price + '元'
  66. })
  67. }
  68. this.data.columns.push(column);
  69. }
  70. this.$update();
  71. },
  72. leave: function(){
  73. this.supr();
  74. }
  75. });
  76. return App;
  77. });

现在可以这样写:

  1. import template from './app.html';
  2. import IndexCache from 'pro/cache/indexCache';
  3. import userUtil from 'pro/util/userUtil';
  4. import Module from 'pro/module/module';
  5. import util from 'pro/util/util';
  6. const App = Module.extend({
  7. template: template,
  8. config: function () {
  9. this.supr();
  10. this.cache = new IndexCache();
  11. Object.assign(this.data, {
  12. columns: [],
  13. columnConfig: [{
  14. isShowIntroPic: false,
  15. isVertical: false
  16. }, {
  17. isShowIntroPic: true,
  18. isVertical: true
  19. }, {
  20. isShowIntroPic: true,
  21. isVertical: true
  22. }, {
  23. isShowIntroPic: false,
  24. isVertical: false
  25. }],
  26. courseUrlPrefix: userUtil.isUserLogin() ? this.$urlPrefix.termDetailPrefix : this.$urlPrefix.courseDetailPrefix
  27. });
  28. this.data.courseUrl = '/path/courses/';
  29. },
  30. init: function () {
  31. this.supr();
  32. this.getInitData();
  33. },
  34. enter: function () {
  35. this.supr();
  36. },
  37. getInitData: async function () {
  38. const data = await this.cache.courseColumn();
  39. for(let [columnIdx, columnData] of data.entries()) {
  40. let columnConfig = this.data.columnConfig[columnIdx],
  41. column = {
  42. title: columnData.sectionName,
  43. isShowIntroPic: columnConfig.isShowIntroPic,
  44. isVertical: columnConfig.isVertical,
  45. introPicSrc: columnData.photoUrl,
  46. courseCards: []
  47. },
  48. coursesData = columnData.termCardVos;
  49. for(let courseData of coursesData) {
  50. column.courseCards.push({
  51. title: courseData.courseName,
  52. url: this.data.courseUrlPrefix.replace(':termid', courseData.termId),
  53. src: courseData.bigPhoto,
  54. price: courseData.price == 0 ? '免费' : `${courseData.price}元`
  55. });
  56. }
  57. this.data.columns.push(column);
  58. }
  59. this.$update();
  60. },
  61. leave: function () {
  62. this.supr();
  63. }
  64. });
  65. export {
  66. App
  67. };

本文来自网易云社区,经作者曹阳授权发布。

原文地址:nej+regular环境使用es6的低成本方案

更多网易研发、产品、运营经验分享请访问网易云社区

  1.  

nej+regular环境使用es6的低成本方案的更多相关文章

  1. 理解Docker(6):若干企业生产环境中的容器网络方案

    本系列文章将介绍 Docker的相关知识: (1)Docker 安装及基本用法 (2)Docker 镜像 (3)Docker 容器的隔离性 - 使用 Linux namespace 隔离容器的运行环境 ...

  2. 将手机micro USB口转换为USB type C连接器的低成本方案

    我们知道USB IF提出的type C连接器的终极目标是统一各种USB 接口. 尽管USB 3.0在PC市场上发展的风生水起,但是由于USB 3.0对手机4G LTE的EMI和RFI干扰,导致市场上除 ...

  3. 检测当前运行环境对es6的支持

    摘自:http://es6.ruanyifeng.com/#docs/intro 1.查看 node 已经实现的 es6 特性 // Linux & Mac $ node --v8-optio ...

  4. Linux企业生产环境用户权限集中管理项目方案案例

    企业生产环境用户权限集中管理项目方案案例: 1 问题现状 当前我们公司里服务器上百台,各个服务器上的管理人员很多(开发+运维+架构+DBA+产品+市场),在大家登录使用Linux服务器时,不同职能的员 ...

  5. MySQL分库分表环境下全局ID生成方案 转

    在大型互联网应用中,随着用户数的增加,为了提高应用的性能,我们经常需要对数据库进行分库分表操作.在单表时代,我们可以完全依赖于数据库的自增ID来唯一标识一个用户或数据对象.但是当我们对数据库进行了分库 ...

  6. MySQL分库分表环境下全局ID生成方案

    在大型互联网应用中,随着用户数的增加,为了提高应用的性能,我们经常需要对数据库进行分库分表操作.在单表时代,我们可以完全依赖于数据库的自增ID来唯一标识一个用户或数据对象.但是当我们对数据库进行了分库 ...

  7. 【转】MySQL分库分表环境下全局ID生成方案

    转载一篇博客,里面有很多的知识和思想值得我们去思考. —————————————————————————————————————————————————————————————————————— 在大 ...

  8. nginx+php负载均衡集群环境中的session共享方案梳理

    在网站使用nginx+php做负载均衡情况下,同一个IP访问同一个页面会被分配到不同的服务器上,如果session不同步的话,就会出现很多问题,比如说最常见的登录状态. 下面罗列几种nginx负载均衡 ...

  9. 【转】生产环境:Nginx高可用方案

    准备工作: 192.168.16.128 192.168.16.129 两条虚拟机.安装好 Nginx 安装Nginx 更新 yum 源文件: rpm -ivh http://nginx.org/pa ...

随机推荐

  1. iOS常用动画 类封装

    //这是一个很好的动画封装类 很容易明白很详细 和大家分享 // CoreAnimationEffect.h // CoreAnimationEffect // // Created by Vince ...

  2. a different object with the same identifier value was already associated with the session解决方案

    org.springframework.orm.hibernate3.HibernateSystemException: a different ]; nested exception ] at or ...

  3. cdh 安装步骤

    http://www.cnblogs.com/jasondan/p/4011153.html 关于CDH和Cloudera Manager CDH (Cloudera's Distribution, ...

  4. 高德地图-android 权限设置

    转自http://blog.csdn.NET/eyu8874521/article/details/8481953 一个Android应用程序需要权限才能调用某些android系统的功能:一个andr ...

  5. 启动图。引导页以及EAIntroView的使用

    ios启动图: 1242 x 2208 (6plus)    R5.5位置 750 x 1334   (6)           R4.7位置 640 x 960     (4/4s)      2x ...

  6. IDEA 提示找不到 javax 等 tomcat 的相关包

    网上很多方法都告诉你,把 javax 的 libs 拷贝到项目下吧,简直简单粗暴.其实有更好的办法. 1.首先进入 Run 其中的 Run/Debug Configurations,在 Server ...

  7. 653. Two Sum IV - Input is a BST 二叉树版本

    [抄题]: Given a Binary Search Tree and a target number, return true if there exist two elements in the ...

  8. C++代码静态分析工具splint

    1.引言 最近在项目中使用了静态程序分析工具PC-Lint, 体会到它在项目实施中带给开发人员的方便.PC-Lint是一款针对C/C++语言.windows平台的静态分析工具,FlexeLint是针对 ...

  9. easyui页签更新

    1.首先引入这个js文件 <script src="/Scripts/tabs.js" type="text/javascript"></sc ...

  10. 51Nod 1554 欧姆诺姆和项链 (KMP)

    题意:中文题. 析:首先要使用KMP的失配函数 f ,对于长度为 i 的串,如果存在循环节那么  i % (i-f[i]) == 0,循环节的长度就是 i - f[i] ,当然次数就是 i / (i- ...