предисловие
Внешний вид NodeJs, поэтому концепция фронтенд-инжиниринга углубляется. Сначала принесли Gulp, Webpack и другие мощные инструменты сборки, а потом снова появилисьvue-cliа такжеcreate-react-appТакие идеальные леса обеспечивают полную структуру проекта, позволяя нам больше сосредоточиться на бизнесе, не тратя много времени на инфраструктуру проекта.
Тем не менее, эти готовые леса могут не соответствовать потребностям нашего бизнеса и не могут быть лучшей практикой.Я считаю, что у каждой крупной компании есть леса, разработанные на заказ.Сегодня давайте почитаем о JD.comNutUIВстроенные леса в библиотеке компонентовNutUI-CLI
Введение в интерфейс командной строки NutUI
NutUI-CLIЭто инструмент для создания библиотеки компонентов Vue, с помощью которого вы можете создать набор библиотек компонентов Vue.
Функция
- запуск локальной отладки devОфициальный сайт и демонстрационный пример
- добавить Быстро создать матч
NutUIстандартные компоненты - build строит библиотеку компонентов, создавая библиотеку, которую можно использовать дляКод компонента для производственной среды
- библиотека компонентов сборки сайта сборкиОфициальный сайт + Демонстрационный пример сайта
- ...
Для того, чтобы все быстро поняли внутреннюю логику, я разобрала карту мозга для вашего ознакомления.
Исходный адрес NutUI-CLIGitHub.com/BOE2Oh/Angry Back…
Конкретную последовательность выполнения программы можно разделить на入口命令脚本接受/分发器 > 命令接收器 > 编译逻辑处理 > webpack配置
1. Приемщик/распространитель командного сценария входа
Как CLI вызывается в NUTUI
Я полагаю, что вы знакомы со следующими командами @vue/cli scaffolding
$ npm run serve
{
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
}
}
То же самое и в NutUI
$ npm run dev
"scripts": {
"dev": "nutui-cli dev",
"build": "nutui-cli build",
"build:site": "nutui-cli build-site",
"add": "nutui-cli add"
},
Мы видим, что фактическая команда, выполняемая vue/cli,
$ vue-cli-service serve
Фактическая команда, выполняемая NutUI,
$ nutui-cli dev
Теперь давайте подумаем об этом,vue-cli-serviceа такжеnutui-cliКак эта команда воспринимается нашим проектом?
Итак, на этом этапе я собираюсь сказать несколько слов.
process.argv для Node.js
Свойство process.argv возвращает массив, содержащий аргументы командной строки, переданные при запуске процесса Node.js. Первый элемент — это process.execPath. Если вам нужно получить доступ к необработанному значению argv[0], см. process.argv0. Вторым элементом будет путь к исполняемому файлу JavaScript.
Например, предположим, что сценарий для process-args.js выглядит следующим образом:
// 打印 process.argv。
process.argv.forEach((val, index) => {
console.log(`${index}: ${val}`);
});
Запустите процесс Node.js:
$ node process-args.js one two=three four
Результат выглядит следующим образом:
0: /usr/local/bin/node
1: /Users/mjr/work/node/process-args.js
2: one
3: two=three
4: four
Проверьте package.json в каталоге CLI.
cli/package.json
...
"bin": {
"nutui-cli": "./dist_cli/bin/index.js"
},
"scripts": {
"dev": "tsc --watch --incremental"
}
...
package.jsonсерединакорзина собственностиИспользуется для указания местоположения исполняемого файла, соответствующего каждой внутренней команде, мы можем видеть приведенную выше конфигурацию.nutui-cliСоответствующий скрипт, выполняемый командой,./dist_cli/bin/index.jsТогда давайте проверим соответствующее местоположение в github в это время.
Обнаружено, что такой директории нет, что происходит, вдумайтесь 🤔, мы обнаружили, что в директории src внутри CLI нет файлов js, все файлы TypeScript.
😯~~ Оказывается, мы можем узнатьpackage.jsonв сценарияхdev : tsc --watch --incrementalЕсть эта команда. Поскольку в исходном коде есть эта команда dev, я запущу ее первой.
// 进入cli 源码目录
$ cd lib/plugin/cli/
// 安装依赖...
$ yarn
// 安装完成后执行dev命令
$ npm run dev
В этот момент посмотрите на папку в проекте и обнаружите, что у dist_cli есть
тогда 🤔 этоtscЧто это, после поиска,tscПо сути, это команда компиляции TypeScript, которая преобразует файл ts в js.TypeScriptИзtsconfig.json, откройте сначалаtsconfig.jsonпосмотри
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"declaration": true,
"sourceMap": true,
"outDir": "./dist_cli",
"strict": true,
"moduleResolution": "node",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true
},
"include": ["src/**/*"],
"exclude": [
"node_modules"
]
}
Мы видим ключевой моментoutDirпредставляет выходной путь,includeПредставляет путь компиляции.
Документация>tsconfig.json
Если вы не понимаете TS, вы должны поторопиться и сделать это.Исходный код в Vue3.0 в основном TS, что показывает важность TS.
Советы: Вот два многословных предложения. Это то, что мы хотим сделать в процессе чтения исходного кода. Нам нужно докопаться до сути и разбить поиск кастрюли (Baidu, Google) до конца. Если Вы не понимаете, идите в поиск, чтобы узнать.
хорошо, мы уже поняли основной синтаксис TS выше, давайте действительно прочитаем его
входной командный скрипт accept/distributor bin/index.ts
#!/usr/bin/env node
import { setNodeEnv } from '../util';
process.argv[2] === 'dev' ? setNodeEnv('development') : setNodeEnv('production');
import program from 'commander';
import { dev } from '../commands/dev';
import { build } from '../commands/build';
import { buildSite } from '../commands/build-site';
...
const config = require(ROOT_CLI_PATH('package.json'));
program.version(`@nutui/cli ${config.version}`, '-v', '--version')
program.command('dev')
.description('本地调试运行官网和Demo示例')
.action(dev)
program.command('build')
.description('构建完整版nutui和各个组件可发布到npm的静态资源包')
.action(build)
program.command('build-site')
.description('构建官网和Demo示例,进行官网发布')
.action(buildSite)
...
program.parse(process.argv);
Мы анализируем один за другим
#!/usr/bin/env node
Команду необходимо разместить на первой строке, иначе она не сработает
При написании пакета npm его необходимо указать в первой строке скрипта (обязательно), чтобы указать, что файл скрипта должен выполняться с использованием node.
/usr/bin/env используется, чтобы указать пользователю перейти в каталог пути для поиска узла, узел #!/usr/bin/env позволяет системе динамически находить узел, что решило проблему несогласованных настроек для разных пользователей. на разных машинах.
Инструменты для команды nodecommander
Мы видим, что строка 4 в nutui представляет стороннюю библиотеку:commander
import program from 'commander';
Нетрудно понять с точки зрения общего кода,commanderконтролировать ввод пользователяdev build...дождитесь команды, затем перейдите к триггеруactionСоответствующий метод указал на. На данный момент, я считаю, что все хорошо знают модуль распределителя команд, поэтому код в
import { dev } from '../commands/dev';
import { build } from '../commands/build';
соответствующийdev buildи т. д. метод, мы можем видеть, что все изcommands 命令接收器Введенный в модуль, далее мы представим приемник команд
2. Приемник команд
советы: мы можем пройти
ctrl|commandклавиша + щелчок левой кнопкой мыши для быстрого перехода к соответствующему методу
Здесь разбираются только три команды модуля, остальные команды примерно такие же, если интересно, можете их тоже прочитать.
Отладка dev.ts локально
import { compileSite } from '../compiler/site';
export async function dev() {
await compileSite();
}
сборка сайта build-site.ts
import { emptyDir } from 'fs-extra';
import { compileSite } from '../compiler/site';
import { DIST_DIR } from "../util/dic";
export async function buildSite() {
await emptyDir(DIST_DIR);
await compileSite(true);
process.exit()
}
import { emptyDir } from 'fs-extra';
fs-extraПакет операций с файлами, но больше введения, вы можете посмотретьДокументация
Мы в основном смотрим наdev.tsа такжеbuildSite.tsтакже цитируется вcompileSiteКлючевой метод, просто переданные параметры разные, мы знаем из введения функцииdevЭто отладка официального веб-сайта + демонстрационный пример локально, иbuild-siteЭто сборка официального сайта + демонстрационный пример, тут примерно можно догадатьсяcompileSiteАргументы должны различать сборки dev и build соответственно. Посмотрим позжеcompileSiteлогика.
Сборка библиотеки компонентов build.ts
import { emptyDir } from 'fs-extra';
import { compilePackage } from '../compiler/package';
import { DIST_DIR } from "../util/dic";
import logger from '../util/logger';
import { compilePackageDisperse } from '../compiler/package.disperse';
export async function build() {
try {
await emptyDir(DIST_DIR);
await compilePackage(false);
logger.success(`build compilePackage false package success!`);
await compilePackage(true);
logger.success(`build compilePackage true package success!`);
await compilePackageDisperse();
logger.success(`build compilePackageDisperse package success!`);
process.exit();
} catch (error) {
logger.error(error)
}
}
buildЗдесь интересно, соответственноcompilePackageа такжеcompilePackageDisperseЭти два важных метода
а такжеcompilePackageа такжеcompileSiteСпособ использования аналогичен, передавать отдельноtrue false,compilePackageDisperseвызывается напрямую.
compileSite,compilePackage,compilePackageDisperseЭти три важных метода вытекают изcompilerЕсли модуль получен, то войдите в следующий раздел, чтобы интерпретировать один за другим.
3. Логическая обработка компиляции
Компиляция сайта site.ts > compileSite
import { devConfig } from '../webpack/dev.config';
import { prodConfig } from '../webpack/prod.config';
import { compileWebPack } from './webpack';
import logger from '../util/logger';
export async function compileSite(prod: boolean = false) {
try {
prod ? await compileWebPack(prodConfig) : compileWebPack(devConfig);
prod && logger.success('build site success!');
} catch (error) {
logger.error(error);
}
}
Пакет компиляции библиотеки компонентов.ts> compilePackage
import { packageConfig } from '../webpack/package.config';
import { compileWebPack } from './webpack';
export function compilePackage(isMinimize: boolean) {
return compileWebPack(packageConfig(isMinimize))
}
Загружать библиотеку компонентов по требованию
import { compileWebPack } from './webpack';
import { packageDisperseConfig } from '../webpack/package.disperse.config';
export function compilePackageDisperse() {
return compileWebPack(packageDisperseConfig())
}
мы можем видеть вышеdevConfig,prodConfig,packageConfigЭти три конфига извлекаются по запросу из папки webpack по очереди.На самом деле ключевой момент в том чтоwebpack.ts > compileWebPackСюда
Это высший приоритет, отвечающий за основную компиляцию
Основная сборка webpack.ts
import Webpack from 'webpack';
import WebpackDevServer from 'webpack-dev-server';
import logger from "../util/logger";
function devServer(config: Webpack.Configuration) {
const compiler = Webpack(config);
const devServerOptions = {
...
};
const server = new WebpackDevServer(compiler, devServerOptions);
server.listen(8001, '0.0.0.0', (err: Error) => {
if (err) logger.error(err);
});
}
function build(config: Webpack.Configuration) {
return new Promise((resolve, reject) => {
Webpack(config, (err: any, stats) => {
if (err || stats.hasErrors()) {
// 在这里处理错误
...
reject(err || stats.toJson())
} else {
// 处理完成
resolve();
}
});
});
}
export function compileWebPack(config: Webpack.Configuration) {
switch (config.mode) {
case "development":
devServer(config);
break;
case "production":
return build(config);
}
}
Мы видим, что webpack.ts открыт извне.export function compileWebPackМетод ввода принудительно ограничиваетсяWebpack.ConfigurationТипы
Это место в полной мере воплощает в себе преимущества, принесенные мне его сильными ограничениями, и его метод внутренне проходитconfig.modeсвойства различатьdevServerещеbuildПостроить.
Увидев это, мне интересно, заметили ли вы, что в верхней части файла есть 2 строки кода.
import Webpack from 'webpack';
import WebpackDevServer from 'webpack-dev-server';
Здесь нужно сказать, что в традиционном
vue/cli2Когда вызывается эшафот, это происходит через следующееWebPack CLIКоманда вызывается в проекте package.json
"scripts": {
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"start": "npm run dev",
"build": "node build/build.js"
}
В NutUI-CLI это делается черезWebPack Node API WebpackDevServer,WebpackЧтобы позвонить, то какая польза звонить таким образом.
ЦитироватьwebpackОфициальная документация: webpack предоставляет Node.js API, который можно использовать непосредственно в среде выполнения Node.js. API Node.js очень полезен, когда вам нужно настроить процесс сборки или разработки, потому что на этом этапе все отчеты и обработка ошибок должны быть реализованы вами, а веб-пакет отвечает только за часть компиляции. Таким образом, параметр конфигурации статистики не будет действовать в вызове webpack().
Итак, теперь мы знаем, что compileWebPack отвечает только за компиляцию, все файлы извлекаются из папки webpack, далее мы переходим к следующей главе конфигурации WebPack.
4. Конфигурация веб-пакета
base.config.ts,dev.config.ts,prod.config.tsЭти три файла конфигурации в основном используются для отладки и упаковки веб-сайтов. Обычно они используются во всех проектах. Я не буду подробно о них рассказывать. Вы можете использовать webpack.официальная документацияЧтобы понять, мы в основном говорим оpackage.config.tsа такжеpackage.disperse.config.tsЭти два файла конфигурации в основном используются для создания библиотек компонентов.
package.config.ts
import Webpack from 'webpack';
import merge from 'webpack-merge';
import { baseConfig } from './base.config';
...
export function packageConfig(isMinimize: boolean) {
const _packageConfig: Webpack.Configuration = {
mode: 'production',
devtool: 'source-map',
entry: {
nutui: ROOT_PACKAGE_PATH('src/nutui.js'),
},
output: {
path: ROOT_PACKAGE_PATH('dist/'),
filename: isMinimize ? '[name].min.js' : '[name].js',
library: '[name]',
libraryTarget: 'umd',
umdNamedDefine: true,
// https://stackoverflow.com/questions/49111086/webpack-4-universal-library-target
globalObject: `(typeof self !== 'undefined' ? self : this)`
},
externals: {
vue: {
root: 'Vue',
commonjs: 'vue',
commonjs2: 'vue',
amd: 'vue'
}
},
...
};
isMinimize && _packageConfig.plugins?.push(new OptimizeCSSAssetsPlugin())
return merge(baseConfig, _packageConfig);
}
-
entry: { nutui: ROOT_PACKAGE_PATH('src/nutui.js'), }src/nutui.jsУпаковать все компоненты в единый пакет -
output.libraryTarget: 'umd'- Предоставьте свою библиотеку как работающую для всех определений модулей. Он будет работать в среде CommonJS, AMD или экспортировать модуль в глобальную переменную. Для получения дополнительной информации см.UMDсклад. - При использовании libraryTarget: "umd" установите:
umdNamedDefine: trueМодули AMD названы в процессе сборки UMD. В противном случае используется анонимный определяющий. -
output.externals: { vue: { root: 'Vue', commonjs: 'vue', commonjs2: 'vue', amd: 'vue' } }- root: доступ к библиотеке можно получить через глобальную переменную (например, через тег скрипта).
- commonjs: к библиотеке можно получить доступ как к модулю CommonJS.
- commonjs2: аналогично приведенному выше, но экспортирует module.exports.default.
- amd: похож на commonjs, но использует модульную систему AMD.
-
output.globalObjectПри нацеливании на библиотеку, особенно когда libraryTarget имеет значение «umd», этот параметр указывает, какой глобальный объект будет использоваться для загрузки библиотеки. Чтобы сборка UMD была доступна как в браузере, так и в Node.js, установите для параметра output.globalObject значение «this».
Использование этого файла конфигурации для упаковки может создать файл js, который мы представляем глобально.
хорошо, мы разобрались с принципом внедрения компонентов глобально, далее будем анализировать и подгружать каждый дефолтный конфигурационный файл по требованию.package.disperse.config.
package.disperse.config.ts
import Webpack from 'webpack';
import merge from 'webpack-merge';
import { ROOT_PACKAGE_PATH } from '../util/dic';
import { baseConfig } from './base.config';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import CopyWebpackPlugin from 'copy-webpack-plugin';
import OptimizeCSSAssetsPlugin from 'optimize-css-assets-webpack-plugin';
const confs = require(ROOT_PACKAGE_PATH('src/config.json'));
export function packageDisperseConfig() {
const entry: any = {};
confs.packages.map((item: any) => {
const cptName: string = item.name.toLowerCase();
entry[cptName] = ROOT_PACKAGE_PATH(`src/packages/${cptName}/index.js`);
});
const _packageDisperseConfig: Webpack.Configuration = {
mode: 'production',
devtool: 'source-map',
entry,
...
plugins: [
...
new CopyWebpackPlugin([
{
from: ROOT_PACKAGE_PATH('src/'),
to: ROOT_PACKAGE_PATH('dist/'),
ignore: ['demo.vue', 'doc.md', 'config.json', 'nutui.js', '*.spec.js']
}
]),
new CopyWebpackPlugin([
{
from: ROOT_PACKAGE_PATH('types/'),
to: ROOT_PACKAGE_PATH('dist/types/')
}
]),
]
};
return merge(baseConfig, _packageDisperseConfig);
}
Основной код использует каждый компонент как запись для формирования нескольких записей.
const confs = require(ROOT_PACKAGE_PATH('src/config.json'));
const entry: any = {};
confs.packages.map((item: any) => {
const cptName: string = item.name.toLowerCase();
entry[cptName] = ROOT_PACKAGE_PATH(`src/packages/${cptName}/index.js`);
});
Мы видим, чтоconfs.packagesсередина
"packages": [
{
"name": "Cell",
"version": "1.0.0",
"sort": "4",
"chnName": "列表项",
"type": "component",
"showDemo": true,
"desc": "列表项,可组合成列表",
"author": "Frans"
},
{
"name": "Dialog",
"version": "1.0.0",
"sort": "2",
"chnName": "对话框",
"type": "method",
"showDemo": true,
"desc": "模态弹窗,支持按钮交互,支持图片弹窗。",
"star": 5,
"author": "Frans"
},
...
]
js и css каждого компонента могут быть построены с использованием слишком большого количества записей.
Затем нам действительно нужно представить исходный код vue в режиме разработки dev, и как исходный код упакован, см.pluginсерединаCopyWebpackPluginплагин
Исходный код относительно прост, скопируйте его напрямую, а затем выполните тест упаковки.
plugins: [
new MiniCssExtractPlugin({
filename: '[name]/[name].css'
}),
new OptimizeCSSAssetsPlugin(),
new CopyWebpackPlugin([
{
from: ROOT_PACKAGE_PATH('src/'),
to: ROOT_PACKAGE_PATH('dist/'),
ignore: ['demo.vue', 'doc.md', 'config.json', 'nutui.js', '*.spec.js']
}
]),
new CopyWebpackPlugin([
{
from: ROOT_PACKAGE_PATH('types/'),
to: ROOT_PACKAGE_PATH('dist/types/')
}
]),
]
Упаковано успешно, полный пакет статических ресурсов npm
Суммировать
Основная цель этой статьи — побудить вас более активно читать и изучать исходный код, помочь вам узнать больше о vue и webpack и сделать нас лучшими разработчиками. Регулярно тратить несколько часов на чтение исходного кода неэффективно в краткосрочной перспективе, но в долгосрочной перспективе это очень полезно. Прочитав исходный код, вы получите более глубокое понимание того, как работает фреймворк, который вы обычно используете, что, в свою очередь, облегчит вам его более эффективное использование и расширение. Это поможет вам сократить время завершения от требований до кодирования.
Наконец, прикрепитеNUTUIОфициальный сайт библиотеки компонентовnutui.jd.com