使用模块化工具打包自己开发的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'); 复制代码
控制台直接报错。。。
原因很简单:打包出来的 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); 复制代码我们导出的对象挂载到了
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'); 复制代码
你可能注意到,我打包时并没有使用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
– 通用模块定义,以 amd
, cjs
和 iife
为一体
使用配置文件
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 模块
于 webpack
和 Browserify
不同, 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)对比总结》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Android模块化改造以及模块化通信框架
- Laravel 模块化开发模块 – Caffienate
- ASP.NET Core模块化前后端分离快速开发框架介绍之4、模块化实现思路
- 前端模块化架构设计与实现(二|模块接口设计)
- JavaScript模块化
- 前端模块化总结
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。