近來有個需求,須要為小順序寫一個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/
}
]
}
注重點:
- 日常平凡開闢調試sdk時能夠直接
webpack -w
- 末了打包時,運用
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的。
假如你有興緻,能夠看一下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()
]
};
但是如許運轉會報錯,由於uglify插件只支撐es5的緊縮,而我此次開闢的sdk並不須要轉成es5,所以換一個插件
npm i rollup-plugin-terser -D
import { terser } from 'rollup-plugin-terser';
export default {
input: './src/main.js',
output: {
file: './dist/monitor.js',
format: 'cjs'
},
plugins: [
terser()
]
};
固然,你也能夠運用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