生产环境兼容前端新特性改造

生产环境兼容前端新特性改造

一个新接手的项目。为了日后开发时的好心情,我对这个项目做了一些改造,以更适应现在的潮流。

  • 生产环境兼容前端新特性改造
    • -背景
    • -babel
      • 配置文件
        • presets
        • plugins
        • 编译顺序
      • 使用方法
        • 命令行
        • gulp
    • -rollup
      • 开始
      • 用在gulp中
      • Tree-shaking

-背景

  • 项目兼容性:ie9以上
  • 项目框架:angular 1.x

原来的项目代码中可能也是为了兼容ie9,最多也就用到了es5的语法,看着还是挺难受的。明明map方法里面可以用诸如

xxx.map(item => item * 2)

这样的语法写的更简洁,却还是得用老语法

xxx.map(function(item){
    return item * 2
})

然后代码量就多了两行了。

-babel

Babel是一个广泛使用的转码器,可以将ES6代码转为ES5代码,从而在现有环境执行。

以下是一个例子:

// 转码前
input.map(item => item + 1);

// 转码后
input.map(function (item) {
  return item + 1;
});

网上关于babel介绍的博客有很多。比如阮一峰的babel入门教程,韩小平的博客的如何写好.babelrc?Babel的presets和plugins配置解析等,这些都是很好的入门教程。

这里综合以上教程和我的理解,稍微简单介绍一下

配置文件

babel的配置文件名字是.babelrc,一般放在需要生效的目录下,可以直接放在根目录下起到全局设置的作用。
当使用到babel转换时,这个文件提供配置的依据,其格式如下:

{
  "presets": [],
  "plugins": []
}

presets

presets字段为转码规则,常用的配置有以下几种

presets名称 含义
es2015、es2016…es20xx 使用es20xx的相关语法插件
stage-0、stage-1…stage-4 使用stage-x提案阶段的语法插件,数字越小,阶段越前
latest 包括了es2015往后的所有插件
react 允许使用flow,jsx等语法
env 自动模式

对应的npm安装包格式为

babel-preset-${name}

其中${name}代表presets名称

plugins

plugins字段为babel的插件,可以通过插件达到很多特殊语法的处理。
以下介绍几种常见的插件

transform-runtime
options如下

  • helpers: boolean,默认true
    使用babel的helper函数
  • polyfill: boolean,默认true
    使用babel的polyfill,但是不能完全取代babel-polyfill。
  • regenerator: boolean,默认true
    使用babel的regenerator。
  • moduleName: string,默认babel-runtime
    使用对应module处理。

*transform-runtime中的polyfill这个参数我们下面还会提到

编译顺序

  • plugins优先于presets进行编译
  • plugins按照数组的index增序(从数组第一个到最后一个)进行编译
  • presets按照数组的index倒序(从数组最后一个到第一个)进行编译

使用方法

命令行

babel提供了cli工具,用于命令行转码

npm i -g babel-cli

# 转码结果输出到标准输出
$ babel example.js

# 转码结果写入一个文件
# --out-file 或 -o 参数指定输出文件
$ babel example.js --out-file compiled.js
# 或者
$ babel example.js -o compiled.js

# 整个目录转码
# --out-dir 或 -d 参数指定输出目录
$ babel src --out-dir lib
# 或者
$ babel src -d lib

# -s 参数生成source map文件
$ babel src -d lib -s

gulp

使用npm安装gulp插件

npm i gulp-babel -D

配置gulp命令

const babel = require('gulp-babel')

gulp.task('babel', function () {
    return gulp.src(path.join(jsDir, '/**/*'))
        .pipe(babel({
            presets: [
                ["latest", {
                    "modules": false
                }]
            ]
        }))
        .pipe(gulp.dest(path.join(buildDir, jsDir)))
})

注意:这里用到的latest预设,需要下载下来对应的包
在命令行中输入gulp babel就可以看到效果了

使用上面的配置,还有浏览器的Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局api无法正常转码。这里需要引入上面介绍到的插件transform-runtime。

const babel = require('gulp-babel')

gulp.task('babel', function () {
    return gulp.src(path.join(jsDir, '/**/*'))
        .pipe(babel({
            presets: [
                ["latest", {
                    "modules": false
                }]
            ],
            plugins: [
                ["transform-runtime",
                    {
                        "helpers": false,
                        "polyfill": true,
                        "regenerator": true,
                        "moduleName": "babel-runtime"
                    }]
            ]
        }))
        .pipe(gulp.dest(path.join(buildDir, jsDir)))
})

这里polyfill传了true,解决了babel的latest预设不能对那些全局api转码的问题。

使用babel,我们解决了大部分的语法问题,但是es6的import,export这种引用外部文件的语法没办法兼容。这里我们需要使用到打包工具。

-rollup

说到打包,大家一般马上会想起webpack。

在上面的背景里谈到,项目结构属于angular 1.x的应用,项目中使用了angular指令、路由等很多特色语法以及非常多的html模板。我暂时没有在网上找到合适的loader去解析这些angular的语法,因此把目光放到了每个文件各自解析自己的思路上。这个想法跟webpack初衷相反,因此这里使用不了它。

rollup这个打包工具是通过和其他同事闲聊的时候得知的。抱着试一试的心态看了下文档,发现它擅长将小块的代码组合起来,正好适合这个项目使用。

开始

使用npm安装rollup

npm install rollup –global

rollup的配置文件默认叫做rollup.config.js
以下是它的配置模板

// rollup.config.js
export default {
  input: 'src/main.js',
  output: {
    file: 'bundle.js',
    format: 'cjs'
  }
}

input指定文件入口,output指定出口和转换方式。
在命令行中输入

rollup -c

就开始了文件打包
如果需要指定其他文件,可以在命令行后加上文件名,如

rollup --config rollup.config.dev.js
rollup --config rollup.config.prod.js

这里输出的格式是cjs,还有其他几种

  • amd – 异步模块定义,用于像RequireJS这样的模块加载器
  • cjs – CommonJS,适用于 Node 和 Browserify/Webpack
  • es – 将软件包保存为ES模块文件
  • iife – 一个自动执行的功能,适合作为script标签。(如果要为应用程序创建一个捆绑包,您可能想要使用它,因为它会使文件大小变小。)
  • umd – 通用模块定义,以amd,cjs 和 iife 为一体

更多参数请查阅官网

用在gulp中

从上面的语法可以看出,rollup打包出来的文件非常精简,如果提供多个config,可以达到单个文件各自为政的效果。正好gulp插件有这么一个工具,叫做gulp-better-rollup提供了这个功能。
因此我们可以这么写在gulp文件当中

const rollup = require('gulp-better-rollup')
const resolve = require('rollup-plugin-node-resolve')
const commonjs = require('rollup-plugin-commonjs')

let rollupJsDist = function (gulpSrc) {
    return gulpSrc
        .pipe(rollup({
            plugins: [commonjs(), resolve({
                // 将自定义选项传递给解析插件
                customResolveOptions: {
                    moduleDirectory: 'node_modules'
                }
            })]
        }, 'umd'))
        .pipe(sourcemaps.write())
}

gulp.task('rollupJs', function () {
    return rollupJsDist(gulp.src(path.join(jsDir, '/**/*.js')))
        .pipe(gulp.dest(path.join(buildDir, jsDir)));
})

这里给rollup传入了两个插件,rollup-plugin-node-resolve和rollup-plugin-commonjs,用来解析Node.js里面的CommonJS模块。

另外,这个gulp插件支持babel,因此结合上部分对于gulp里babel的部分,我们可以再改写一下

gulpfile.js:

const rollup = require('gulp-better-rollup')
const resolve = require('rollup-plugin-node-resolve')
const commonjs = require('rollup-plugin-commonjs')
const babel = require('rollup-plugin-babel')

let rollupJsDist = function (gulpSrc) {
    return gulpSrc
        .pipe(rollup({
            plugins: [commonjs(), resolve({
                // 将自定义选项传递给解析插件
                customResolveOptions: {
                    moduleDirectory: 'node_modules'
                }
            }), babel()]
        }, 'umd'))
        .pipe(sourcemaps.write())
}

gulp.task('rollupJs', function () {
    return rollupJsDist(gulp.src(path.join(jsDir, '/**/*.js')))
        .pipe(gulp.dest(path.join(buildDir, jsDir)));
})

.babelrc:

{
  "presets": [
    [
      "latest",
      {
        "es2015": {
          "modules": false
        }
      }
    ]
  ],
  "plugins": [
    "external-helpers",
    "lodash",
    [
      "transform-runtime",
      {
        "helpers": false,
        "polyfill": true,
        "regenerator": true,
        "moduleName": "babel-runtime"
      }
    ]
  ]
}

以上加入了babel的插件,babel的配置写在了外部文件。其中babelrc文件里面根据官方介绍babel增加了一些东西:

  • 首先,我们设置”modules”: false,否则 Babel 会在 Rollup 有机会做处理之前,将我们的模块转成 CommonJS,导致 Rollup 的一些处理失败
  • 其次,我们使用external-helpers插件,它允许 Rollup 在包的顶部只引用一次 “helpers”,而不是每个使用它们的模块中都引用一遍(这是默认行为)
  • 第三,我们将.babelrc文件放在src中,而不是根目录下。 这允许我们对于不同的任务有不同的.babelrc配置,比如像测试,如果我们以后需要的话 - 通常为单独的任务单独配置会更好

根据需求,我在配置里面加了lodash的插件,允许lodash按需加载项目中所使用的函数,可以参考这里对插件的介绍

Tree-shaking

rollup打包出来的文件非常精简,它只在乎那些用得到的东西,把打包前后的文件对比你能非常直观的发现它加进去的所有东西。

// the-answer.js
var index = 42;

export default index;

比如当我用import语法引入the-answer.js这个文件

// main.js
import answer from 'the-answer';

export default function () {
  console.log('the answer is ' + answer);
}

最后打包的结果为

// bundle.js
'use strict';

var index = 42;

function main () {
  console.log('the answer is ' + index);
}

module.exports = main;
//# sourceMappingURL=bundle.js.map

可以看到,rollup只是将其中index=42的部分拿了出来。
这个合并的技术官方称之为Tree-shaking

你可能感兴趣的:(js)