Blog address
Recently there was a requirement to write an SDK for applets to monitor API calls and page errors (similar to fundebug).
The SDK is just a JS file, similar to the third-party library we introduced during development:
const moment = require('moment');
moment().format();
Copy the code
The modularization of small program adopts Commonjs specification. In other words, I need to provide a monitor.js file that supports Commonjs and can be imported in the applets entry file app.js:
/ / import the SDK
const monitor = require('./lib/monitor.js');
monitor.init('API-KEY');
// Normal business logic
App({
...
})
Copy the code
So the question is, how do I develop this SDK? (Note: this article does not specifically discuss how to implement monitor applets.)
There are several options: for example, simply write all the logic in a monitor.js file and export it
module.exports = {
// All kinds of logic
}
Copy the code
However, considering the amount of code, in order to reduce coupling, I prefer to split the code into different modules and finally package all JS files into a monitor.js. Those of you who have used Vue and React development can appreciate the benefits of modular development.
Demo code download
The following directory structure is defined:
The SRC directory holds the source code, and the dist directory packs the final monitor.js
SRC /main.js SDK entry file
import { Engine } from './module/Engine';
let monitor = null;
export default {
init: function (appid) {
if(! appid || monitor) {return;
}
monitor = newEngine(appid); }}Copy the code
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('Start listening for applets ~~~'); }}Copy the code
src/util/index.js
export const util = {
generateId() {
return Math.random().toString(36).substr(2); }}Copy the code
So, how do I package all this JS into a final monitor.js file that will execute correctly?
webpack
The first thing THAT came to my mind was webpack. After all, work is often developed with React, and it was the last thing I used to package projects.
Based on webPack 4.x
npm i webpack webpack-cli --save-dev
Copy the code
Relying on my shallow knowledge of webpack metaphysics, I tearfully wrote down a few lines of configuration: 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',}};Copy the code
When you run Webpack, the package is packaged, but try introducing it into a small application
Applets entry file app.js
var monitor = require('./dist/monitor.js');
Copy the code
The console reported an error…
monitor.js
eval
All we need to do is change the Devtool for the WebPack configuration
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'
};
Copy the code
Source-map mode does not use the eval keyword for debugging, and instead generates an additional monitor.js.map file for debugging
Webpack again, import applets, and the problem is again:
var monitor = require('./dist/monitor.js');
console.log(monitor); / / {}
Copy the code
It prints out an empty object!
src/main.js
import { Engine } from './module/Engine';
let monitor = null;
export default {
init: function (appid) {
if(! appid || monitor) {return;
}
monitor = newEngine(appid); }}Copy the code
Monitor.js does not export an object with an init method!
We wanted monitor.js to conform to the CommonJS specification, but we didn’t specify that in the configuration, so nothing was exported from the webPack package.
In our normal development, we do not need to export a variable when packaging, as long as the packaged file can be executed immediately on the browser. Look at any Vue or React project and see what the entry file looks like.
main.js
import Vue from 'vue'
import App from './App'
new Vue({
el: '#app'.components: { App },
template: '<App/>'
})
Copy the code
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.js';
ReactDOM.render(
<App />,
document.getElementById('root')
);
Copy the code
Are there any similar routines that end up executing a method immediately without exporting a variable?
libraryTarget
The libraryTarget is the key, and by setting this property we let WebPack know which specification to use to export a variable
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'
};
Copy the code
Commonjs2 is what we want the CommonJS specification to be
Repackage it, and it’ll be right this time
var monitor = require('./dist/monitor.js');
console.log(monitor);
Copy the code
The object we exported is mounted on the default property because when we exported it:
export default {
init: function (appid) {
if(! appid || monitor) {return;
}
monitor = newEngine(appid); }}Copy the code
Now we are happy to import the SDK
var monitor = require('./dist/monitor.js').default;
monitor.init('45454');
Copy the code
You may have noticed that I did not use Babel when I packaged it, because the applet supports ES6 syntax, so there is no need to go through the process of developing this SDK. If you want to develop a library that is browser-compatible, you can add a Babel-loader
module: {
rules: [{test: /\.js$/.loader: 'babel-loader'.exclude: /node_modules/}}]Copy the code
Note:
- Usually development debugging SDK can be directly
webpack -w
- When finally packing, use
webpack -p
Be compressed
The full 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' // Applets do not support eval-source-map
};
Copy the code
In fact, using WebPack pure JS class library is very simple, than we usually develop an application, configuration is much less, after all, there is no need to pack CSS, HTML, images, fonts these static resources, also do not need to load on demand.
rollup
The article could have ended here, but when I was investigating how to package modules, I took a look at how Vue and React package code, and it turned out that they both used rollup instead of Webpack.
Rollup is a JavaScript module wrapper that compiles small pieces of code into large, complex pieces of code, such as libraries or applications.
Rollup is used to package libraries.
If you’re interested, take a look at the webpack package of Monitor.js and ask, “What is this mess of code?”
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: {}
// Omit 10,000 lines of code below
Copy the code
Webpack implements its own __webpack_exports__ __webpack_require__ module mechanism
/ * * * / "./src/util/index.js":
/ *! * * * * * * * * * * * * * * * * * * * * * * * * * * *! * \! *** ./src/util/index.js ***! A \ * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/ *! 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); }}/ * * * / })
Copy the code
It wraps each JS file in a function, implementing reference and export between modules.
If you use rollup packaging, you’ll be surprised to find that the readability of packaged code is not in the same league as webPack!
npm install --global rollup
Copy the code
Create a new rollup.config.js file
export default {
input: './src/main.js'.output: {
file: './dist/monitor.js'.format: 'cjs'}};Copy the code
Format: CJS specifies that the packaged file complies with the CommonJS specification
Run a rollup – c
An error is reported, saying [!] Error: Could not resolve ‘.. /util’ from src\module\Engine.js
This is because rollup recognizes.. /util/ does not automatically look for the index.js file in util (webpack does that by default), so we need to change it to.. /util/index
Packaged files:
'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('Start listening for applets ~~~'); }}let monitor = null;
var main = {
init: function (appid) {
if(! appid || monitor) {return;
}
monitor = newEngine(appid); }}module.exports = main;
Copy the code
Super neat!
And when importing, there is no need to write a default attribute webpack package
var monitor = require('./dist/monitor.js').default;
monitor.init('45454');
Copy the code
A rollup packaging
var monitor = require('./dist/monitor.js');
monitor.init('45454');
Copy the code
Similarly, we can roll up-c-W directly during normal development and compress it when we pack it
npm i rollup-plugin-uglify -D
Copy the code
import { uglify } from 'rollup-plugin-uglify';
export default {
input: './src/main.js'.output: {
file: './dist/monitor.js'.format: 'cjs'
},
plugins: [
uglify()
]
};
Copy the code
The uglify plugin only supports ES5 compression, and the SDK I developed this time doesn’t need to be converted to ES5, so I need a new plugin
npm i rollup-plugin-terser -D
Copy the code
import { terser } from 'rollup-plugin-terser';
export default {
input: './src/main.js'.output: {
file: './dist/monitor.js'.format: 'cjs'
},
plugins: [
terser()
]
};
Copy the code
Of course, you can also use Babel transcoding
npm i rollup-plugin-terser babel-core babel-preset-latest babel-plugin-external-helpers -D
Copy the code
.babelrc
{
"presets": [["latest", {
"es2015": {
"modules": false}}]],"plugins": ["external-helpers"]}Copy the code
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()
]
};
Copy the code
UMD
The SDK we just packaged doesn’t use any environment-specific apis, which means that this code can actually run on both node and browser sides.
If we want packaged code to be compatible with all platforms, we need to comply with the UMD specification (compatible with 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()
]
};
Copy the code
By setting format and name, we can package monitor.js to be compatible with a variety of runtime environments
On the node
var monitor = require('monitor.js');
monitor.init('6666');
Copy the code
On the browser side
<script src="./monitor.js"></srcipt>
<script>
monitor.init('6666');
</srcipt>
Copy the code
The principle is actually very simple, you can take a look at the packaged source code, or read an article I wrote before
conclusion
Rollup is usually used to package JS libraries, with smaller code and no redundant code. Rollup supports ES6 modularity only by default. If you want to support Commonjs, you need to download the plugin rollup-plugin-commonjs
Webpack is usually great for packaging an app, so if you need Code Splitting or you have a lot of static resources to work with, consider using WebPack