【构建】定制SDK打包工具(gulp版本)

1,752 阅读6分钟

现在流行 mvvm 三大框架,各种cli 工具也给我们提供了很多方便。我在工作中使用的是vue 技术栈。vue-cli 让我不能更爽了。分分钟拉起架子,开始各种业务代码。

但是,工作中我还遇到另一种情况,就是我需要写一些 SDK 文件,有对内的也有对外的。每次写这个都让我蛋疼的不要不要的,如果使用 cli ,需要依赖,文件太大不合适。如果手工去写,好多es6的语法用不上,还得自己处理 polyfill。 索性根据需要自己搭建一套脚手架。

需求

开始之前,我们先分析下需要哪些功能。SDK 一般都是提供一些Api方法,其中绝大多数可能都是JS。所以我们可能需要以下的一些能力。

  • ES6 转义为 ES5 语法
  • 有可能需要分模块,需要处理 import
  • 压缩

开始

webpackgulp 都是目前比较流行的构建工具。这次我们使用 gulp 来构建。

首先新建一个空的文件sdk-cli。然后执行以下命令,初始化一个package.json 文件。

npm init -y

下一步,我们全局和本地安装gulp。

npm install -g gulp
npm install gulp --save-dev

我们在跟目录下新建一个 ``gulpfile.js

// gulpfile.js
const gulp = require('gulp');

同时,我们在根目录下创建一个app.js 文件。

//  app.js   测试代码
let SDK = {
    show: () =>{
        console.log('this is function')
    },
    init(options){
        let _opt = {
            name:'zhangsan',
            age: 23
        }
        options = Object.assign(_opt,options);
        console.log(options);
    }
}

window.SDK = SDK;

编译ES6

下一步,我们开始编译上面的app.js 文件,最后输出。

编译ES6,我们就需要用到 gulp-babel 这个插件了。

npm install gulp-babel --save-dev

同时,我们需要创建一个 .babelrc 的文件,这个文件是定义了一些 babel 的规则。

// .babelrc
{
	"presets": [
		[
			"@babel/preset-env",
			{
				"useBuiltIns": "usage",  //根据代码的需要加载polyfill
				"corejs": 3  // 依赖corejs-3
			}
		]
	]
}

主要是把es6的语法转换为es5的语法。

下面我们往 gulpfile.js 中添加一些代码。

const gulp = require('gulp');
const babel = require('gulp-babel');

function js(){
    return gulp.src('./app.js') // 输入 app.js
        .pipe(babel())		// babel 转换代码 
        .pipe(gulp.dest('./dist/'))	  	// 输出到dist文件夹下,会自动创建文件夹
}
exports.js = js; // 输出task任务为js

然后我们执行构建代码

gulp js

然后我们会看到报错

$ gulp js
internal/modules/cjs/loader.js:638
    throw err;
    ^

Error: Cannot find module '@babel/core'
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:636:15)
    at Function.Module._load (internal/modules/cjs/loader.js:562:25)
    at Module.require (internal/modules/cjs/loader.js:690:17)
    at require (internal/modules/cjs/helpers.js:25:18)
    at Object.<anonymous> (D:\hlmy\learning\sdk-clis\node_modules\gulp-babel\index.js:7:15)
    at Module._compile (internal/modules/cjs/loader.js:776:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:787:10)
    at Module.load (internal/modules/cjs/loader.js:653:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:593:12)
    at Function.Module._load (internal/modules/cjs/loader.js:585:3)

这是因为虽然我们安装了 gulp-babel ,他只是一个工具库,他还需要用到 @babel/core 这个核心库。同样道理,也需要安装@babel/preset-env

npm install @babel/core @babel/preset-env -D

安装完之后,我们在试着跑下命令

gulp js

我们会看到这次成功了,在 dist 文件夹下输出了一个 app.js文件。

他是长这样的,

"use strict";

require("core-js/modules/es.object.assign");

//  app.js   测试代码
var SDK = {
  show: function show() {
    console.log('this is function');
  },
  init: function init(options) {
    var _opt = {
      name: 'zhangsan',
      age: 23
    };
    options = Object.assign(_opt, options);
    console.log(options);
  }
};
window.SDK = SDK;

跟我们的源文件比一下,会发现他把一些es6的语法,比如 箭头函数 , 对象写法 转换成了 es5的语法。

babel 转换是分为两部分,一个是转换语法,一个是转换 api。默认情况下直接转换语法,api部分是通过polyfill来实现的。

例如: Object.assign 就是通过引入require("core-js/modules/es.object.assign"); 来解决。

这是因为,babel 默认是转换成commonjs 格式的,这是啥意思呢,就是说他最终转换成能在node 服务中使用。

如果我们想要在浏览器中再次能使用,就得再次处理。比如下面这个插件。

browserify

npm install browserify vinyl-source-stream -D

我们在改一下 gulpfile.js 文件

const gulp = require('gulp');
const babel = require('gulp-babel');
const browserify = require('browserify');
const source = require('vinyl-source-stream');

function js(){
    return gulp.src('./app.js') // 输入 app.js
        .pipe(babel())		// babel 转换代码 
        .pipe(gulp.dest('./dist/'))	  	// 输出到dist文件夹下,会自动创建文件夹
}

function browser(){
    var b = browserify({
        entries: "dist/app.js"
    });
    return b.bundle()
        // 将常规流转换为包含 Stream 的 vinyl 对象
        .pipe(source("bundle.js"))
        .pipe(gulp.dest("dist/"));
}

exports.js = js; // 输出task任务为js
exports.browser = browser;

这次我们引入了两个插件browserifyvinyl-source-stream

browserify 就是把 commonjs 格式转换成 浏览器可用的文件。功能很强大,我们这里主要使用的是他的基本功能。

vinyl-source-stream 这个插件是把常规的数据流转换成包含 Streamvinyl 对象。

数据流

我们都知道 gulp 是流处理,但是它的流和 我们通常所说的 node 的流不是一个东西。gulp的流是基于 vinyl , 这是一个虚拟对象格式。

我们去 vinyl 的官网看下,他的用法是这样的。

var Vinyl = require('vinyl');

var jsFile = new Vinyl({
  cwd: '/',
  base: '/test/',
  path: '/test/file.js',
  contents: new Buffer('var x = 123')
});

vinyl格式除了内容外,还包含一些路径的信息。它的contents总共有三种格式,Stream Buffernull

而gulp 默认使用的是 包含 Buffervinyl 对象。

vinyl的作用

那为什么gulp 不使用普通的 node 的Stream流呢,而要自己弄一个 vinyl 虚拟格式呢?我们看一下如下代码:

function css(){
    gulp.src('./css/**/*.css')
    	.pipe(gulp.dest('./dist'))
}
exports.css = css;

这段代码的意思是,把css目录下的所有css文件,复制到dist目录下。

请注意这个包含多级目录,我们知道gulp会自动创建没有的目录和文件名。而node 的stream 流呢,他只传输Stream Buffer这些,不关心路径的问题。所以就需要使用 vinyl 格式。

其实 vinyl 虚拟格式是一个对普通流的包装。

安装core-js

言归正传,我们接着写构建。如果你现在去运行构建命令 gulp browser,是会报错的。为什么呢,因为我们还需要安装core-js 。gulp 的 polyfill 都是通过这个库包含的,所以我们需要安装。

npm install core-js -D

然后我们在运行一下

gulp browser

我们打开 dist看一下,下面有两个文件app.jsbundle.js

bundle.js 使我们最终所需要的文件。

扫尾工作

主要转换工作,已经做完。剩下我们还需要一些扫尾工作,比如 压缩

上面是两步操作,所以我们也需要合并,删除一些中间文件。

最终如下:

// gulpfile.js

const gulp = require('gulp');
// 编译es6
const babel = require('gulp-babel');
// 编译commonjs
const browserify = require('browserify');
// 转换为stream的vinyl对象
const source = require('vinyl-source-stream');
// 转换为buffer的vinyl对象
const buffer = require('vinyl-buffer');
// 压缩
const uglify = require("gulp-uglify");
// 清除文件
const clean = require('gulp-clean');

function js(){
    return gulp.src('./app.js') // 输入 app.js
        .pipe(babel())		// babel 转换代码 
        .pipe(gulp.dest('./dist/'))	  	// 输出到dist文件夹下,会自动创建文件夹
}

function browser(){
    var b = browserify({
        entries: "dist/app.js"
    });
    return b.bundle()
        // 将常规流转换为包含 Stream 的 vinyl 对象
        .pipe(source("bundle.js"))
        // 将 vinyl 对象内容中的 Stream 转换为 Buffer
        .pipe(buffer())
        // 压缩
        .pipe(uglify())
        .pipe(gulp.dest("dist/"));
}

// 删除中间文件
async function del(){
    await gulp.src('./dist/app.js')
        .pipe(clean())
}

exports.js = js; // 输出task任务为js
exports.browser = browser;
// 顺序执行task
exports.es = gulp.series( js,browser,del)

差不多到现在为止,一个简单的构建就出来了。可用满足使用了。

另外,gulpbabel browserify 都是很灵活的。所以以上的用法只是其中的一个,大家如果有兴趣也可以尝试用其他的一些插件来完成。整体思路是这样。

如果大家对 构建工具开发SDK,有什么好的方法或者新的。可以留言一起探讨。

所有的代码在 github上,有需要可以自己查看。

参考链接:

segmentfault.com/a/119000000…

zhuanlan.zhihu.com/p/58624930

github.com/browserify/…

gulpjs.com/docs/en/get…

www.babeljs.cn/docs/