使用模块化工具打包自己开发的JS库(webpack/rollup)对比总结

栏目: JavaScript · 发布时间: 5年前

内容简介:最近有个需求,需要为小程序写一个SDK,监控小程序的后台接口调用和页面报错(类似fundebug)听起来高大上的SDK,其实就是一个JS文件,类似平时开发中我们引入的第三方库:小程序的模块化采用了Commonjs规范。也就是说,我需要提供一个

最近有个需求,需要为小程序写一个SDK,监控小程序的后台接口调用和页面报错(类似fundebug)

听起来高大上的SDK,其实就是一个JS文件,类似平时开发中我们引入的第三方库:

const moment = require('moment');
moment().format();
复制代码

小程序的模块化采用了Commonjs规范。也就是说,我需要提供一个 monitor.js 文件,并且该文件需要支持Commonjs,从而可以在小程序的入口文件 app.js 中导入:

// 导入sdk
const monitor = require('./lib/monitor.js');
monitor.init('API-KEY');

// 正常业务逻辑
App({
    ...
})
复制代码

所以问题来了,我应该怎么开发这个SDK? (注意:本文并不具体讨论怎么实现监控小程序)

方案有很多种:比如直接把所有的逻辑写在一个 monitor.js 文件里,然后导出

module.exports = {
    // 各种逻辑
}
复制代码

但是考虑到代码量,为了降低耦合度,我还是倾向于把代码拆分成不同模块,最后把所有JS文件打包成一个 monitor.js 。平时有使用过Vue和React开发的同学,应该能体会到模块化开发的好处。

src目录下存放源代码,dist目录打包最后的 monitor.js

src/main.js SDK入口文件

import { Engine } from './module/Engine';

let monitor = null;

export default {
    init: function (appid) {
        if (!appid || monitor) {
            return;
        }
        monitor = new Engine(appid);
    }
}
复制代码
// src/module/Engine.js

import { util } from '../util/';

export class Engine {
    constructor(appid) {
        this.id = util.generateId();
        this.appid = appid;
        this.init();
    }

    init() {
        console.log('开始监听小程序啦~~~');
    }
}
复制代码
// src/util/index.js

export const util = {
    generateId() {
        return Math.random().toString(36).substr(2);
    }
}
复制代码

所以,怎么把这堆js打包成最后的 monitor.js 文件,并且程序可以正确执行?

webpack

我第一个想到的就是用webpack打包,毕竟工作经常用React开发,最后打包项目用的就是它。 基于webpack4.x版本

npm i webpack webpack-cli --save-dev
复制代码

靠着我对于webpack玄学的微薄知识,含泪写下了几行配置: webpack.config.js

var path = require('path');
var webpack = require('webpack');
module.exports = {
    mode: 'development',
    entry: './src/main.js',
    output: {
        path: path.resolve(__dirname, './dist'),
        publicPath: '/dist/',
        filename: 'monitor.js',
    }
};
复制代码

运行 webpack ,打包倒是打包出来了,但是引入到小程序里试试

小程序入口文件 app.js

var monitor = require('./dist/monitor.js');
复制代码

控制台直接报错。。。

使用模块化 <a href='https://www.codercto.com/tool.html'>工具</a> 打包自己开发的JS库(webpack/rollup)对比总结

原因很简单:打包出来的 monitor.js 使用了 eval 关键字,而小程序内部并支持eval。

我们只需要更改webpack配置的devtool即可

var path = require('path');
var webpack = require('webpack');
module.exports = {
    mode: 'development',
    entry: './src/main.js',
    output: {
        path: path.resolve(__dirname, './dist'),
        publicPath: '/dist/',
        filename: 'monitor.js',
    },
    devtool: 'source-map'
};
复制代码

source-map 模式就不会使用 eval 关键字来方便 debug ,它会多生成一个 monitor.js.map 文件来方便debug

再次 webpack 打包,然后倒入小程序,问题又来了:

var monitor = require('./dist/monitor.js');
console.log(monitor); // {}
复制代码

打印出来的是一个空对象!

//src/main.js

import { Engine } from './module/Engine';

let monitor = null;

export default {
    init: function (appid) {
        if (!appid || monitor) {
            return;
        }
        monitor = new Engine(appid);
    }
}
复制代码

monitor.js 并没有导出一个含有init方法的对象!

我们期望的是 monitor.js 符合commonjs规范,但是我们在配置中并没有指出,所以webpack打包出来的文件,什么也没导出。

我们平时开发中,打包时也不需要导出一个变量,只要打包的文件能在浏览器上立即执行即可。你随便翻一个Vue或React的项目,看看入口文件是咋写的?

main.js

import Vue from 'vue'
import App from './App'

new Vue({
  el: '#app',
  components: { App },
  template: '<App/>'
})
复制代码
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.js';

ReactDOM.render(
    <App />,
  document.getElementById('root')
);
复制代码

是不是都类似这样的套路,最后只是立即执行一个方法而已,并没有导出一个变量。

libraryTarget

libraryTarget就是问题的关键,通过设置该属性,我们可以让webpack知道使用何种规范导出一个变量

var path = require('path');
var webpack = require('webpack');
module.exports = {
    mode: 'development',
    entry: './src/main.js',
    output: {
        path: path.resolve(__dirname, './dist'),
        publicPath: '/dist/',
        filename: 'monitor.js',
        libraryTarget: 'commonjs2'
    },
    devtool: 'source-map'
    
};
复制代码

commonjs2 就是我们希望的commonjs规范

重新打包,这次就正确了

var monitor = require('./dist/monitor.js');
console.log(monitor);
复制代码
使用模块化工具打包自己开发的JS库(webpack/rollup)对比总结
我们导出的对象挂载到了 default

属性上,因为我们当初导出时:

export default {
    init: function (appid) {
        if (!appid || monitor) {
            return;
        }
        monitor = new Engine(appid);
    }
}
复制代码

现在,我们可以愉快的导入SDK

var monitor = require('./dist/monitor.js').default;
monitor.init('45454');
复制代码
使用模块化工具打包自己开发的JS库(webpack/rollup)对比总结

你可能注意到,我打包时并没有使用babel,因为小程序是支持es6语法的,所以开发该sdk时无需再转一遍,如果你开发的类库需要兼容浏览器,则可以加一个babel-loader

module: {
        rules: [
            {
                test: /\.js$/,
                loader: 'babel-loader',
                exclude: /node_modules/
            }
        ]
    }
复制代码

注意点:

1,平时开发调试 sdk 时可以直接 webpack -w 2,最后打包时,使用 webpack -p 进行压缩

完整的webpack.config.js

var path = require('path');
var webpack = require('webpack');
module.exports = {
    mode: 'development', // production
    entry: './src/main.js',
    output: {
        path: path.resolve(__dirname, './dist'),
        publicPath: '/dist/',
        filename: 'monitor.js',
        libraryTarget: 'commonjs2'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                loader: 'babel-loader',
                exclude: /node_modules/
            }
        ]
    },
    devtool: 'source-map' // 小程序不支持eval-source-map
};
复制代码

其实,使用webpack打包纯JS类库是很简单的,比我们平时开发一个应用,配置少了很多,毕竟不需要打包css,html,图片,字体这些静态资源,也不用按需加载。

rollup

文章写到这里本来可以结束了,但是在前期调研如何打包模块的时候,我特意看了下Vue和React是怎么打包代码的,结果发现,这俩都没使用webpack,而是使用了rollup。

Rollup 是一个 JavaScript 模块打包器,可以将小块代码编译成大块复杂的代码,例如 library 或应用程序。

Rollup官网的这段介绍,正说明了rollup就是用来打包library的。 www.rollupjs.com/guide/zh#-u…

如果你有兴趣,可以看一下 webpack 打包后的 monitor.js ,绝对会吐槽,这一坨代码是啥东西?

module.exports =
/******/ (function(modules) { // webpackBootstrap
/******/     // The module cache
/******/     var installedModules = {};
/******/
/******/     // The require function
/******/     function __webpack_require__(moduleId) {
/******/
/******/         // Check if module is in cache
/******/         if(installedModules[moduleId]) {
/******/             return installedModules[moduleId].exports;
/******/         }
/******/         // Create a new module (and put it into the cache)
/******/         var module = installedModules[moduleId] = {
/******/             i: moduleId,
/******/             l: false,
/******/             exports: {}


// 以下省略1万行代码
复制代码

webpack自己实现了一套 __webpack_exports__ __webpack_require__ module 机制

/***/ "./src/util/index.js":
/*!***************************!*\
  !*** ./src/util/index.js ***!
  \***************************/
/*! exports provided: util */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "util", function() { return util; });
const util = {
    generateId() {
        return Math.random().toString(36).substr(2);
    }
}

/***/ })
复制代码

它把每个js文件包裹在一个函数里,实现模块间的引用和导出。

如果使用 rollup 打包,你就会惊讶的发现,打包后的代码可读性简直和webpack不是一个级别!

npm install --global rollup
复制代码

新建一个 rollup.config.js

export default {
  input: './src/main.js',
  output: {
    file: './dist/monitor.js',
    format: 'cjs'
  }
};
复制代码

format: cjs 指定打包后的文件符合commonjs规范

运行 rollup -c

这时会报错,说 [!] Error: Could not resolve '../util' from src\module\Engine.js

这是因为,rollup识别 ../util/ 时,并不会自动去查找util目录下的 index.js 文件( webpack 默认会去查找),所以我们需要改成 ../util/index

打包后的文件:

'use strict';

const util = {
    generateId() {
        return Math.random().toString(36).substr(2);
    }
};

class Engine {
    constructor(appid) {
        this.id = util.generateId();
        this.appid = appid;
        this.init();
    }

    init() {
        console.log('开始监听小程序啦~~~');
    }
}

let monitor = null;

var main = {
    init: function (appid) {
        if (!appid || monitor) {
            return;
        }
        monitor = new Engine(appid);
    }
}

module.exports = main;
复制代码

是不是超简洁!

而且导入的时候,无需再写个default属性 webpack 打包

var monitor = require('./dist/monitor.js').default;
monitor.init('45454');
复制代码

rollup打包

var monitor = require('./dist/monitor.js');
monitor.init('45454');
复制代码

同样,平时开发时我们可以直接 rollup -c -w ,最后打包时,也要进行压缩

npm i rollup-plugin-uglify -D
复制代码
import { uglify } from 'rollup-plugin-uglify';
export default {
  input: './src/main.js',
  output: {
    file: './dist/monitor.js',
    format: 'cjs'
  },
  plugins: [
    uglify()
  ]
};
复制代码

当然,你也可以使用babel转码

npm i rollup-plugin-terser babel-core babel-preset-latest babel-plugin-external-helpers -D
复制代码

.babelrc

{
  "presets": [
    ["latest", {
      "es2015": {
        "modules": false
      }
    }]
  ],
  "plugins": ["external-helpers"]
}
复制代码

rollup.config.js

import { terser } from 'rollup-plugin-terser';
import babel from 'rollup-plugin-babel';
export default {
    input: './src/main.js',
    output: {
        file: './dist/monitor.js',
        format: 'cjs'
    },
    plugins: [
        babel({
            exclude: 'node_modules/**'
        }),
        terser()
    ]
};
复制代码

UMD

我们刚刚打包的SDK,并没有用到特定环境的API,也就是说,这段代码,其实完全可以运行在node端和浏览器端。

如果我们希望打包的代码可以兼容各个平台,就需要符合UMD规范(兼容AMD,CMD, Commonjs, iife)

import { terser } from 'rollup-plugin-terser';
import babel from 'rollup-plugin-babel';
export default {
    input: './src/main.js',
    output: {
        file: './dist/monitor.js',
        format: 'umd',
        name: 'monitor'
    },
    plugins: [
        babel({
            exclude: 'node_modules/**'
        }),
        terser()
    ]
};
复制代码

通过设置format和name,这样我们打包出来的 monitor.js 就可以兼容各种运行环境了

在node端

var monitor = require('monitor.js');
monitor.init('6666');
复制代码

在浏览器端

<script src="./monitor.js"></srcipt>
<script>
    monitor.init('6666');
</srcipt>
复制代码

原理其实也很简单,你可以看下打包后的源码,或者看我之前写过的一篇文章

总结

rollup通常适用于打包JS类库,通过rollup打包后的代码,体积较小,而且没有冗余的代码。rollup默认只支持ES6的模块化,如果需要支持Commonjs,还需下载相应的插件 rollup-plugin-commonjs

webpack通常适用于打包一个应用,如果你需要代码拆分(Code Splitting)或者你有很多静态资源需要处理,那么可以考虑使用webpack

原文地址: segmentfault.com/a/119000001…

文章二 使用Rollup打包JavaScript

rollup是一款小巧的javascript模块打包工具,更适合于库应用的构建工具;可以将小块代码编译成大块复杂的代码,基于ES6 modules,它可以让你的 bundle 最小化,有效减少文件请求大小,vue在开发的时候用的是webpack,但是最后将文件打包在一起的时候用的是 rollup.js

全局安装

npm install --global rollup
复制代码

开始使用rollup

创建第一个bundle 创建 main.js

console.log(111);
复制代码

执行 rollup --input main.js --output bundle.js --format cjs , 该命令编译 main.js 生成 bundle.js, --format cjs 意味着打包为 node.js 环境代码, 请观察 bundle.js 文件内容

'use strict'
console.log(111);
复制代码

命令行参数简介:

输入(input -i/--input) String 这个包的入口点 (例如:你的 main.js 或者 app.js 或者 index.js ) 文件(file -o/--output.file) String 要写入的文件。也可用于生成 sourcemaps,如果适用 格式(format -f/--output.format) 关于format选项 rollup提供了五种选项:

1,amd – 异步模块定义,用于像RequireJS这样的模块加载器

2, cjs – CommonJS,适用于 Node 和 Browserify/Webpack

3,es – 将软件包保存为ES模块文件

4,iife – 一个自动执行的功能,适合作为 <script> 标签。(如果要为应用程序创建一个捆绑包,您可能想要使用它,因为它会使文件大小变小。)

5, umd – 通用模块定义,以 amdcjsiife 为一体

使用配置文件

rollup.config.js

export default {
    input: 'src/main.js',
    output: {
        file: 'bundle.js',
        format: 'cjs'
    }
};
复制代码

执行 rollup -c rollup.config.js 启动配置项;

rollup 提供了 --watch / -w 参数来监听文件改动并自动重新打包

使用rollup插件

npm install --save-dev rollup-plugin-json
复制代码

我们用的是 --save-dev 而不是 --save,因为代码实际执行时不依赖这个插件——只是在打包时使用。

在配置文件中启用插件

import json from 'rollup-plugin-json';
export default {
    input: './main.js',
    output: {
        file: 'bundle.js',
        format: 'umd'
    },
    plugins: [
        json(),
    ],
}
复制代码

新建文件 data.json

{
    "name": "xiaoming",
    "age": 12
}
复制代码

main.js 引入 data.json

import { name } from './data.json';
console.log(name);
复制代码

执行 rollup -c rollup.config.js ,并查看 bundle.js

(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? factory() :
    typeof define === 'function' && define.amd ? define(factory) :
    (factory());
}(this, (function () { 'use strict';

var name = "xiaoming";

console.log(name);

})));
复制代码

看到bundle中仅引用了data.json中的name字段,这是因为rollup会自动进行 Tree-shaking,main.js中仅引入了name,age并没有没引用,所以age并不会被打包

rollup基础插件

rollup-plugin-alias: 提供modules名称的 alias 和reslove 功能

rollup-plugin-babel: 提供babel能力

rollup-plugin-eslint: 提供eslint能力

rollup-plugin-node-resolve: 解析 node_modules 中的模块

rollup-plugin-commonjs: 转换 CJS -> ESM, 通常配合上面一个插件使用

rollup-plugin-serve: 类比 webpack-dev-server, 提供静态服务器能力

rollup-plugin-filesize: 显示 bundle 文件大小

rollup-plugin-uglify: 压缩 bundle 文件

rollup-plugin-replace: 类比 Webpack 的 DefinePlugin , 可在源码中通过 process.env.NODE_ENV 用于构建区分 Development 与 Production 环境.

rollup于其他工具集成

打包npm 模块

webpackBrowserify 不同, rollup 不会去寻找从npm安装到你的node_modules文件夹中的软件包; rollup-plugin-node-resolve 插件可以告诉 Rollup 如何查找外部模块

npm install --save-dev rollup-plugin-node-resolve
复制代码

打包 commonjs模块

npm中的大多数包都是以CommonJS模块的形式出现的。 在它们更改之前,我们需要将CommonJS模块转换为 ES2015 供 Rollup 处理。 rollup-plugin-commonjs 插件就是用来将 CommonJS 转换成 ES2015 模块的。 请注意, rollup-plugin-commonjs 应该用在其他插件转换你的模块之前 - 这是为了防止其他插件的改变破坏CommonJS的检测

npm install --save-dev rollup-plugin-commonjs
复制代码

使用babel

使用 Babel 和 Rollup 的最简单方法是使用 rollup-plugin-babel

npm install --save-dev rollup-plugin-babel
复制代码

新建.babelrc

{
    "presets": [
        ["latest", {
            "es2015": {
                "modules": false
            }
        }]
    ],
    "plugins": ["external-helpers"]
}
复制代码

1,首先,我们设置"modules": false,否则 Babel 会在 Rollup 有机会做处理之前,将我们的模块转成 CommonJS,导致 Rollup 的一些处理失败

2,我们使用external-helpers插件,它允许 Rollup 在包的顶部只引用一次 “helpers”,而不是每个使用它们的模块中都引用一遍(这是默认行为) 运行 rollup之前, 需要安装latest preset 和external-helpers插件

npm i -D babel-preset-latest babel-plugin-external-helpers
复制代码

一个简单的配置项

import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import babel from 'rollup-plugin-babel';
import json from 'rollup-plugin-json';
export default {
    input: './main.js',
    output: {
        file: 'bundle.js',
        format: 'umd'
    },
    watch: {
        exclude: 'node_modules/**'
    },
    plugins: [
        resolve(),
        commonjs(),
        json(),
        babel({
            exclude: 'node_modules/**',
            plugins: ['external-helpers'],
        }),
    ],
}
复制代码

原文地址: www.jianshu.com/p/6a7413481…

附一份react-redux开源项目的rollup配置文件

import nodeResolve from 'rollup-plugin-node-resolve'     // 帮助寻找node_modules里的包
import babel from 'rollup-plugin-babel'                             // rollup 的 babel 插件,ES6转ES5
import replace from 'rollup-plugin-replace'                       // 替换待打包文件里的一些变量,如 process在浏览器端是不存在的,需要被替换
import commonjs from 'rollup-plugin-commonjs'              // 将非ES6语法的包转为ES6可用
import uglify from 'rollup-plugin-uglify'                              // 压缩包

const env = process.env.NODE_ENV

const config = {
  input: 'src/index.js',
  external: ['react', 'redux'],                           // 告诉rollup,不打包react,redux;将其视为外部依赖
  output: { 
    format: 'umd',                  // 输出 UMD格式,各种模块规范通用
    name: 'ReactRedux',         // 打包后的全局变量,如浏览器端 window.ReactRedux 
    globals: {
      react: 'React',                                         // 这跟external 是配套使用的,指明global.React即是外部依赖react
      redux: 'Redux'
    }
  },
  plugins: [
    nodeResolve(),
    babel({
      exclude: '**/node_modules/**'
    }),
    replace({
      'process.env.NODE_ENV': JSON.stringify(env)
    }),
    commonjs()
  ]
}

if (env === 'production') {
  config.plugins.push(
    uglify({
      compress: {
        pure_getters: true,
        unsafe: true,
        unsafe_comps: true,
        warnings: false
      }
    })
  )
}

export default config
复制代码

以上所述就是小编给大家介绍的《使用模块化工具打包自己开发的JS库(webpack/rollup)对比总结》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Web Security Testing Cookbook

Web Security Testing Cookbook

Paco Hope、Ben Walther / O'Reilly Media / 2008-10-24 / USD 39.99

Among the tests you perform on web applications, security testing is perhaps the most important, yet it's often the most neglected. The recipes in the Web Security Testing Cookbook demonstrate how dev......一起来看看 《Web Security Testing Cookbook》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具