成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

使用模塊化工具打包自己開(kāi)發(fā)的JS庫(kù)

AprilJ / 2973人閱讀

摘要:平時(shí)有使用過(guò)和開(kāi)發(fā)的同學(xué),應(yīng)該能體會(huì)到模塊化開(kāi)發(fā)的好處。原因很簡(jiǎn)單打包出來(lái)的使用了關(guān)鍵字,而小程序內(nèi)部并支持。是一個(gè)模塊打包器,可以將小塊代碼編譯成大塊復(fù)雜的代碼,例如或應(yīng)用程序。官網(wǎng)的這段介紹,正說(shuō)明了就是用來(lái)打包的。

博客地址

最近有個(gè)需求,需要為小程序?qū)懸粋€(gè)SDK,監(jiān)控小程序的后臺(tái)接口調(diào)用和頁(yè)面報(bào)錯(cuò)(類(lèi)似fundebug)

聽(tīng)起來(lái)高大上的SDK,其實(shí)就是一個(gè)JS文件,類(lèi)似平時(shí)開(kāi)發(fā)中我們引入的第三方庫(kù):

const moment = require("moment");
moment().format();

小程序的模塊化采用了Commonjs規(guī)范。也就是說(shuō),我需要提供一個(gè)monitor.js文件,并且該文件需要支持Commonjs,從而可以在小程序的入口文件app.js中導(dǎo)入:

// 導(dǎo)入sdk
const monitor = require("./lib/monitor.js");
monitor.init("API-KEY");

// 正常業(yè)務(wù)邏輯
App({
    ...
})

所以問(wèn)題來(lái)了,我應(yīng)該怎么開(kāi)發(fā)這個(gè)SDK? (注意:本文并不具體討論怎么實(shí)現(xiàn)監(jiān)控小程序)

方案有很多種:比如直接把所有的邏輯寫(xiě)在一個(gè)monitor.js文件里,然后導(dǎo)出

module.exports = {
    // 各種邏輯
}

但是考慮到代碼量,為了降低耦合度,我還是傾向于把代碼拆分成不同模塊,最后把所有JS文件打包成一個(gè)monitor.js。平時(shí)有使用過(guò)Vue和React開(kāi)發(fā)的同學(xué),應(yīng)該能體會(huì)到模塊化開(kāi)發(fā)的好處。

演示代碼下載

如下是定義的目錄結(jié)構(gòu):

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("開(kāi)始監(jiān)聽(tīng)小程序啦~~~");
    }
}

src/util/index.js

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

所以,怎么把這堆js打包成最后的monitor.js文件,并且程序可以正確執(zhí)行?

webpack

我第一個(gè)想到的就是用webpack打包,畢竟工作經(jīng)常用React開(kāi)發(fā),最后打包項(xiàng)目用的就是它。
基于webpack4.x版本

npm i webpack webpack-cli --save-dev

靠著我對(duì)于webpack玄學(xué)的微薄知識(shí),含淚寫(xiě)下了幾行配置:
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",
    }
};

運(yùn)行webpack,打包倒是打包出來(lái)了,但是引入到小程序里試試

小程序入口文件app.js

var monitor = require("./dist/monitor.js");

控制臺(tái)直接報(bào)錯(cuò)。。。

原因很簡(jiǎn)單:打包出來(lái)的monitor.js使用了eval關(guān)鍵字,而小程序內(nèi)部并支持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模式就不會(huì)使用eval關(guān)鍵字來(lái)方便debug,它會(huì)多生成一個(gè)monitor.js.map文件來(lái)方便debug

再次webpack打包,然后倒入小程序,問(wèn)題又來(lái)了:

var monitor = require("./dist/monitor.js");
console.log(monitor); // {}

打印出來(lái)的是一個(gè)空對(duì)象!

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并沒(méi)有導(dǎo)出一個(gè)含有init方法的對(duì)象!

我們期望的是monitor.js符合commonjs規(guī)范,但是我們?cè)谂渲弥胁](méi)有指出,所以webpack打包出來(lái)的文件,什么也沒(méi)導(dǎo)出。

我們平時(shí)開(kāi)發(fā)中,打包時(shí)也不需要導(dǎo)出一個(gè)變量,只要打包的文件能在瀏覽器上立即執(zhí)行即可。你隨便翻一個(gè)Vue或React的項(xiàng)目,看看入口文件是咋寫(xiě)的?
main.js

import Vue from "vue"
import App from "./App"

new Vue({
  el: "#app",
  components: { App },
  template: ""
})
import React from "react";
import ReactDOM from "react-dom";
import App from "./App.js";

ReactDOM.render(
    ,
  document.getElementById("root")
);

是不是都類(lèi)似這樣的套路,最后只是立即執(zhí)行一個(gè)方法而已,并沒(méi)有導(dǎo)出一個(gè)變量。

libraryTarget

libraryTarget就是問(wèn)題的關(guān)鍵,通過(guò)設(shè)置該屬性,我們可以讓webpack知道使用何種規(guī)范導(dǎo)出一個(gè)變量

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規(guī)范

重新打包,這次就正確了

var monitor = require("./dist/monitor.js");
console.log(monitor);

我們導(dǎo)出的對(duì)象掛載到了default屬性上,因?yàn)槲覀儺?dāng)初導(dǎo)出時(shí):

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

現(xiàn)在,我們可以愉快的導(dǎo)入SDK

var monitor = require("./dist/monitor.js").default;
monitor.init("45454");

你可能注意到,我打包時(shí)并沒(méi)有使用babel,因?yàn)樾〕绦蚴侵С謊s6語(yǔ)法的,所以開(kāi)發(fā)該sdk時(shí)無(wú)需再轉(zhuǎn)一遍,如果你開(kāi)發(fā)的類(lèi)庫(kù)需要兼容瀏覽器,則可以加一個(gè)babel-loader

module: {
        rules: [
            {
                test: /.js$/,
                loader: "babel-loader",
                exclude: /node_modules/
            }
        ]
    }

注意點(diǎn):

平時(shí)開(kāi)發(fā)調(diào)試sdk時(shí)可以直接webpack -w

最后打包時(shí),使用webpack -p進(jìn)行壓縮

完整的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
};

其實(shí),使用webpack打包純JS類(lèi)庫(kù)是很簡(jiǎn)單的,比我們平時(shí)開(kāi)發(fā)一個(gè)應(yīng)用,配置少了很多,畢竟不需要打包c(diǎn)ss,html,圖片,字體這些靜態(tài)資源,也不用按需加載。

rollup

文章寫(xiě)到這里本來(lái)可以結(jié)束了,但是在前期調(diào)研如何打包模塊的時(shí)候,我特意看了下Vue和React是怎么打包代碼的,結(jié)果發(fā)現(xiàn),這倆都沒(méi)使用webpack,而是使用了rollup。

Rollup 是一個(gè) JavaScript 模塊打包器,可以將小塊代碼編譯成大塊復(fù)雜的代碼,例如 library 或應(yīng)用程序。

Rollup官網(wǎng)的這段介紹,正說(shuō)明了rollup就是用來(lái)打包library的。

如果你有興趣,可以看一下webpack打包后的monitor.js,絕對(duì)會(huì)吐槽,這一坨代碼是啥東西?

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萬(wàn)行代碼

webpack自己實(shí)現(xiàn)了一套__webpack_exports__ __webpack_require__ module機(jī)制

/***/ "./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);
    }
}

/***/ })

它把每個(gè)js文件包裹在一個(gè)函數(shù)里,實(shí)現(xiàn)模塊間的引用和導(dǎo)出。

如果使用rollup打包,你就會(huì)驚訝的發(fā)現(xiàn),打包后的代碼可讀性簡(jiǎn)直和webpack不是一個(gè)級(jí)別!

npm install --global rollup

新建一個(gè)rollup.config.js

export default {
  input: "./src/main.js",
  output: {
    file: "./dist/monitor.js",
    format: "cjs"
  }
};

format: cjs指定打包后的文件符合commonjs規(guī)范

運(yùn)行rollup -c

這時(shí)會(huì)報(bào)錯(cuò),說(shuō)[!] Error: Could not resolve "../util" from srcmoduleEngine.js

這是因?yàn)?,rollup識(shí)別../util/時(shí),并不會(huì)自動(dòng)去查找util目錄下的index.js文件(webpack默認(rèn)會(huì)去查找),所以我們需要改成../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("開(kāi)始監(jiān)聽(tīng)小程序啦~~~");
    }
}

let monitor = null;

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

module.exports = main;

是不是超簡(jiǎn)潔!

而且導(dǎo)入的時(shí)候,無(wú)需再寫(xiě)個(gè)default屬性
webpack打包

var monitor = require("./dist/monitor.js").default;
monitor.init("45454");

rollup打包

var monitor = require("./dist/monitor.js");
monitor.init("45454");

同樣,平時(shí)開(kāi)發(fā)時(shí)我們可以直接rollup -c -w,最后打包時(shí),也要進(jìn)行壓縮

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()
  ]
};

然而這樣運(yùn)行會(huì)報(bào)錯(cuò),因?yàn)閡glify插件只支持es5的壓縮,而我這次開(kāi)發(fā)的sdk并不需要轉(zhuǎn)成es5,所以換一個(gè)插件

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()
  ]
};

當(dāng)然,你也可以使用babel轉(zhuǎn)碼

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,并沒(méi)有用到特定環(huán)境的API,也就是說(shuō),這段代碼,其實(shí)完全可以運(yùn)行在node端和瀏覽器端。

如果我們希望打包的代碼可以兼容各個(gè)平臺(tái),就需要符合UMD規(guī)范(兼容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()
    ]
};

通過(guò)設(shè)置formatname,這樣我們打包出來(lái)的monitor.js就可以兼容各種運(yùn)行環(huán)境了

在node端

var monitor = require("monitor.js");
monitor.init("6666");

在瀏覽器端

                
閱讀需要支付1元查看
<