使⽤模块化⼯具打包⾃⼰开发的JS库(webpackrollup)对⽐总结
最近有个需求,需要为⼩程序写⼀个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⽂件⾥,然后导出
// 各种逻辑
}
但是考虑到代码量,为了降低耦合度,我还是倾向于把代码拆分成不同模块,最后把所有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 = ateId();
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打包,毕竟⼯作经常⽤React开发,最后打包项⽬⽤的就是它。
基于webpack4.x版本
npm i webpack webpack-cli --save-dev
靠着我对于webpack⽞学的微薄知识,含泪写下了⼏⾏配置:
var path = require('path');
var webpack = require('webpack');
mode: 'development',
entry: './src/main.js',
output: {
path: solve(__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');
mode: 'development',
entry: './src/main.js',
output: {
path: solve(__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';
<App />,
);
是不是都类似这样的套路,最后只是⽴即执⾏⼀个⽅法⽽已,并没有导出⼀个变量。
libraryTarget就是问题的关键,通过设置该属性,我们可以让webpack知道使⽤何种规范导出⼀个变量
var path = require('path');
var webpack = require('webpack');
mode: 'development',
entry: './src/main.js',
output: {
path: solve(__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进⾏压缩
完整的fig.js
var path = require('path');
var webpack = require('webpack');
mode: 'development', // production
entry: './src/main.js',
output: {
path: solve(__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,图⽚,字体这些静态资源,也不⽤按需加载。
⽂章写到这⾥本来可以结束了,但是在前期调研如何打包模块的时候,我特意看了下Vue和React是怎么打包代码的,结果发现,这俩都没使⽤webpack,⽽是使⽤了rollup。
Rollup 是⼀个 JavaScript 模块打包器,可以将⼩块代码编译成⼤块复杂的代码,例如 library 或应⽤程序。
如果你有兴趣,可以看⼀下webpack打包后的monitor.js,绝对会吐槽,这⼀坨代码是啥东西?
/******/ (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
新建⼀个fig.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 = ateId();
this.appid = appid;
this.init();
}
init() {
console.log('开始监听⼩程序啦~~~');
}
}
let monitor = null;
var main = {
init: function (appid) {
if (!appid || monitor) {
return;
}
monitor = new Engine(appid);
}
}
是不是超简洁!
⽽且导⼊的时候,⽆需再写个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"]
}
import { terser } from 'rollup-plugin-terser';
import babel from 'rollup-plugin-babel';
export default {
input: './src/main.js',
output: {
webpack打包流程 面试
file: './dist/monitor.js',
format: 'cjs'
},
plugins: [
babel({
exclude: 'node_modules/**'
}),
terser()
]
};
我们刚刚打包的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');
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论