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的方式存在以下幾個問題:
這個問題可以通過編寫babel插件,根據nej的模塊語法定制修改:babel-plugin-transform-nej-module
除此之外,還遇到一個比較特殊的問題,webpack無法識別我們歷史代碼中的一個文件。
綜上所述,如果有著豐富的webpack使用經驗,能夠承受改變打包方式帶來的風險,可以考慮使用webpack來引入babel。
從webpack的踩坑過程中,找到了做es6/7改造的兩個原則:
?
gulp + babel
根據上述的兩個原則,,gulp無疑是個很好的選擇:
因此,利用gulp來引入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 TDA(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。
分為四步:
?
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的低成本方案
更多網易研發、產品、運營經驗分享請訪問網易云社區。
總結
以上是生活随笔為你收集整理的nej+regular环境使用es6的低成本方案的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vim基础-一般模式
- 下一篇: 人工智能听了很多遍,都应用在哪些领域了你