Используйте модульные инструменты для упаковки библиотек JS, разработанных самостоятельно.

внешний интерфейс JavaScript React.js Webpack

адрес блога

В последнее время возникла необходимость написать SDK для апплета для мониторинга вызовов API и ошибок страниц апплета (аналогично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 в обычное время, должны оценить преимущества модульной разработки.

скачать демо-код

Ниже приведена определенная структура каталогов:

pic

Исходный код хранится в каталоге 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. В конце концов, работа часто разрабатывается, и, наконец, используется пакет.

На основе версии webpack4.x

npm i webpack webpack-cli --save-dev

Опираясь на мои неглубокие знания метафизики веб-пакетов,слезливыйЗапишите несколько строк конфигурации: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.

Нам просто нужно изменить конфигурацию веб-пакета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ключевое слово для облегчения отладки, оно сгенерирует еще одинmonitor.js.mapфайл для облегчения отладки

опять таки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не экспортирует объект с помощью метода инициализации!

на что мы надеемся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-загрузчик

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

будь осторожен:

  1. Вы можете напрямую разрабатывать и отлаживать SDKwebpack -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, оказалось, что ни один из них не использует веб-пакет, а только роллап.

Rollup — это сборщик модулей JavaScript, который компилирует небольшие фрагменты кода в большие сложные фрагменты кода, такие как библиотеки или приложения.

Официальный сайт роллапаЭто введение показывает, что накопительный пакет используется для упаковки библиотеки.

Если вам интересно, вы можете взглянуть на упакованный веб-пакет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 в функцию для получения ссылки и экспорта между модулями.

Если вы используете накопительную упаковку, вы будете удивлены, обнаружив, что читаемость упакованного кода не на том же уровне, что и в веб-пакете!

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

Это связано с тем, что накопительный пакет распознает../util/, он не будет автоматически находить файлы в каталоге utilindex.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;

Разве это не супер просто!

И при импорте не нужно прописывать атрибут по умолчанию упаковка веб-пакета

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

рулонная упаковка

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 конкретной среды, то есть этот код может выполняться на стороне узла и на стороне браузера.

Если мы хотим, чтобы упакованный код был совместим с различными платформами, нам необходимо соответствовать спецификации 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Совместимость с различными операционными средами

на стороне узла

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

на стороне браузера

<script src="./monitor.js"></srcipt>
<script>
    monitor.init('6666');
</srcipt>

Принцип на самом деле очень прост, вы можете посмотреть упакованный исходный код или прочитать тот, который я написал ранее.статья

Суммировать

Rollup обычно подходит для упаковки библиотек классов JS.Код, упакованный с помощью rollup, имеет небольшой размер и не содержит избыточного кода. накопительный пакет по умолчанию поддерживает только модульность ES6.Если вам нужна поддержка Commonjs, вам необходимо загрузить соответствующий плагинrollup-plugin-commonjs

webpack обычно подходит для упаковки приложения, если вам нужно разделение кода (Code Splitting) или у вас есть много статических ресурсов для обработки, то рассмотрите возможность использования webpack