предисловие
Я уже писал немного об основах.строительные леса, оглядываться@vue/cliИсходный код, должен сказать, действительно очень формален и стандартизирован. Обратитесь к некоторым функциям @vue/cli и измените скаффолдинг.
эффект каштана
Структура проекта
проектchuhc-reactСтруктура разделена на четыре части:
-
@chuhc/cliФормируйте содержимое командной строки, инициализируйте проект с помощью команд и т. д. -
@chuhc/scriptsПроект компилирует и запускает упакованное содержимое и еще не подключен к процессу развертывания. -
@chuhc/templateфайл шаблона. -
@chuhc/plugin-xxxПлагины, упакованные в проект, такие как@chuhc/plugin-typescriptи т.п.
cli
непосредственно черезinquirerНекоторые интерактивные команды , после получения информации, проходятdownload-git-repoсоответствоватьgithubВытащить файл шаблона относительно просто, но если шаблонов несколько, обслуживание будет затруднено.
пока в@vue/cliЗдесь он объединяется в общий шаблон с помощью нескольких плагинов.Во многих корневых каталогах скаффолдинга естьxxx.config.jsнезащищенный. затем работаетnodeПри командовании читать конфигурационный файл и выполнять соответствующие операции в соответствии с содержимым конфигурационного файла, например: использоватьwebpack-chainДинамическая модификацияconfig, и, наконец, вызовtoConfigгенерировать новыеwebpackНастроить контент.
Оценка подключаемого модуля, создание пакета
базовыйpackage.jsonШаблоны, кроме обычных константversion,private,licenseподожди, какname,scripts,dependencies,devDependenciesНам нужно добавить его вручную.
nameПросто используйте скаффолдинг для инициализации входящих параметров иscriptsуспешно внедряет@chuhc/scripts, используйте его для запуска команды.
как некоторые обязательные, например.react,react-dom, мы можем прямо положитьdependenciesв, покаdevDependenciesКак правило, он выбирается пользователем вручную во время инициализации.plugins.
с помощьюinquirerПерейдите к списку плагинов и позвольте пользователю выбрать, какие плагины импортировать.
const CHUHC_PLUGIN_CHECK = [
{
name: 'Typescript',
value: ['tsx', '@chuhc/plugin-typescript'],
},
{
name: 'Less',
value: ['less', '@chuhc/plugin-less'],
},
];
const { plugins } = await inquirer.prompt([
{
type: 'checkbox',
name: 'plugins',
message: 'Do you need these plugins',
choices: CHUHC_PLUGIN_CHECK, // 一个结构,自己觉得怎么处理方便,怎么来
},
]);
Тогда в соответствии с вышеизложеннымcodeмогут получить то, что нужно пользователямpluginТак что вы можете поставить этиpluginвставитьjsonизdevDependenciesвнутри.
Получить последнюю версию зависимостей
любой из вышеперечисленныхreactещеplugin, Нам нужен номер версии, я здесь, чтобы использовать командную строку, чтобы получить последнюю версию, а затем как ееvalueстоимость. Если вы перемещаетесь напрямую, чтобы запуститьexecSyncбудет блокировать,oraизloadingТоже застрял, так что выбирайpromiseпробежатьexecперезвонить до концаpromise.
const forEachSetV = (list, obj, key) => {
const promises = [];
const manager = hasCnpm() ? 'cnpm' : 'npm'; // 判断选择cnpm还是npm
list.forEach(item => {
if (typeof item === 'object') {
return forEachSetV(item, obj, key);
}
const newPromise = new Promise(res => {
exec(`${manager} view ${item} version`, (err, stdout, stderr) => {
obj[key][item] = stdout.slice(0, stdout.length - 1);
res(0);
});
});
promises.push(newPromise);
});
return promises;
};
const promise = [
...forEachSetV(depe, pkg, 'dependencies'),
...forEachSetV(devD, pkg, 'devDependencies'),
];
await Promise.all(promise);
Затем, получив номер версии, заполните остальные данные вместе вjson, используйте его какpackage.jsonvalue в новом каталоге проекта создайте его.
const fs = require('fs-extra'); // fs-extra是系统fs模块的扩展
const path = require('path');
module.exports = (dir, files) => {
Object.keys(files).forEach(name => {
const pathName = path.join(dir, name);
fs.ensureDirSync(path.dirname(pathName)); // 如果没有文件夹则新建文件夹
fs.writeFileSync(pathName, files[name]); // 新建文件
});
};
writeFileTree(targetDir, {
'package.json': JSON.stringify(pkg, null, 2),
});
Выберите инструмент управления пакетами
потому чтоnpmСкорость не идеальна и может рассматриваться как практический результат. Сначала определите, имеет ли текущая средаyarn,cnpmИ так далее, а затем приоритеты выбирают первое, если нет, то повторно используйтеnpmработать.
const PM_CONFIG = {
npm: {
install: ['install', '--loglevel', 'error'], // 打印error信息
remove: ['uninstall', '--loglevel', 'error'],
},
yarn: {
install: [],
remove: ['remove'],
},
};
PM_CONFIG.cnpm = PM_CONFIG.npm;
module.exports = class PackageManager {
constructor({ pkgName }) {
this.pkgName = pkgName;
if (hasYarn()) {
this.bin = 'yarn';
} else if (hasCnpm()) {
this.bin = 'cnpm';
} else {
this.bin = 'npm';
}
}
// 封装了下运行命令函数
runCommand(command, args = []) {
const _commands = [this.bin, ...PM_CONFIG[this.bin][command], ...args];
execSync(_commands.join(' '), { stdio: [0, 1, 2] });
}
install() {
try {
this.runCommand('install', ['--offline']); // offline指先去拉取缓存区里的,如果没有则去服务器拉
} catch (e) {
this.runCommand('install'); // 报错兜底
}
}
git() {
try {
execSync('git init');
return true;
} catch (e) {
return false;
}
}
};
судитьyarnа такжеcnpmО том, существует ли он в окружающей среде, можно судить поversionПодождите, пока метод увидит, может ли он быть успешно выполнен, если он успешно выполнен, это означает, что он существует в среде, в противном случае - нет.
const judgeEnv = name => {
const envKey = `_has${name[0].toUpperCase()}${name.slice(1)}`; // 保存下结果
if (_env[envKey] !== null) {
return _env[envKey];
}
try {
execSync(`${name} --version`, { stdio: 'ignore' }); // 不打印信息
return (_env[envKey] = true);
} catch (e) {
return (_env[envKey] = false);
}
};
const hasYarn = judgeEnv.bind(this, 'yarn');
const hasCnpm = judgeEnv.bind(this, 'cnpm');
затем пройтиinstallСпособ установки зависимостей, а затем пройти несколько параметров@chuhc/templateИдите и скопируйте несколько основных шаблонов.
scripts
Поскольку это многостраничный проект,scriptsВ основном он делает следующие вещи:
- пройти через
globчтобы соответствовать записи, затем используйте ее какentryДинамическое входящее и динамическое входящее несколькоhtml-webpack-pluginДатьplugins. - Читая корневой каталог проекта
chuhc.config.jsфайл для динамического измененияwebpackНастройте контент и вызовите соответствующий плагин. - наконец сгенерировать окончательный
webpackконфигурационный файл, переданныйwebpackПерейдите к компиляции, запуску, упаковке и т. д.
вход в матч
Соответствующая запись в основном используетсяglobЧтобы соответствовать, только если требования соответствия будут выполнены, он будет использоваться в качестве записи. Затем через согласованную информацию, чтобы сгенерировать соответствующийentryсодержание иpluginсодержание, переданноеwebpackконфигурационный файл.
const SRC = './src/**/index.?(js|jsx|ts|tsx)';
/**
* get webpack entry
*/
const getEntries = () => {
if (entries) return entries;
entries = {};
const pages = glob.sync(SRC);
pages.forEach(page => { // 遍历传entry
const dirname = path.dirname(page);
const entry = path.basename(dirname);
entries[entry] = page;
});
return entries;
};
/**
* get pages info
* @param {Boolean} isProd
*/
const getPages = isProd => {
const plugins = [];
let entries = getEntries();
Object.keys(entries).map(dirname => { // 遍历传plugin
plugins.push(
new HtmlWebpackPlugin({
chunks: isProd ? ['libs', dirname] : [dirname],
filename: `./${dirname}/index.html`,
template: path.join(__dirname, './template/index.html')
})
);
});
return plugins;
};
цепочка конфигурации конфигурации
Рекомендуется цепная конфигурацияwebpack-chain,@vue/cliтакже использовать его. Поскольку у нас уже есть некоторое базовое содержимое конфигурации, мы можем передатьconfig.mergeОбъедините наш существующий объект конфигурации с экземпляром конфигурации.
Однако прямое преобразование не поддерживается, и нам необходимо вручную преобразовать некоторый контент конфигурации, например:module. а такжеpluginsНе поддерживатьnewизplugin, моя обработка здесь заключается в том, чтобы пропустить вправоplugin, и, наконец, используйтеwebpack-mergeБудуconfig.toConfig()а такжеpluginsА затем объединены в окончательный объект конфигурации.
const Config = require('webpack-chain');
const chuhcConfig = require(`${process.cwd()}/chuhc.config.js`); // 读取根目录的配置文件
const { setBaseConfig } = require('./util/merge'); // 将已有的配置文件对象合并到配置实例
const BASE = require('./config/webpack.base'); // 配置对象base
const DEVE = merge(BASE, require('./config/webpack.deve')); // 配置对象 deve
const PROD = merge(BASE, require('./config/webpack.prod')); // 配置对象 prod
const config = new Config();
// 我这边就只是对plugin做一下处理,可以做其他很多事情,这里只是举个例子
const handleChuhcConfig = ({ plugins } = {}) => {
// to do sth.
if (plugins) {
plugins.forEach(plugin => {
require(plugin[0])(config, plugin[1]);
});
}
};
const getConfig = isDeve => {
config.clear(); // 清除配置
setBaseConfig(isDeve ? DEVE : PROD, config);
handleChuhcConfig(chuhcConfig);
return merge(config.toConfig(), {
plugins: isDeve ? DEVE.plugins : PROD.plugins
}); // 最后再合并
};
скомпилировать и запустить
в полученииwebpack config, то он может быть основан наdevкоманда все ещеbuildкоманду, чтобы вызвать соответствующую функцию, скомпилировать, запустить, упаковать и так далее. (Аналогично, согласноprogram.command)
// dev 运行
const webpack = require('webpack');
const WebpackDevServer = require('webpack-dev-server');
const { getDeveConfig } = require('./config');
module.exports = () => {
const DEVE_CONFIG = getDeveConfig();
const { devServer } = DEVE_CONFIG;
const compiler = webpack(DEVE_CONFIG);
const server = new WebpackDevServer(compiler, { ...devServer });
server.listen(devServer.port);
};
// build
const webpack = require('webpack');
const { getProdConfig } = require('./config');
const logSymbols = require('log-symbols');
module.exports = () => {
const PROD_CONFIG = getProdConfig();
const compiler = webpack(PROD_CONFIG);
compiler.run((err, stats) => {
if (err) {
// 回调中接收错误信息。
console.error(err);
} else {
console.log(logSymbols.success, '打包成功!');
}
});
};
template
templateВ основном через входящие параметры, чтобы определить, следует лиcopyсоответствующий файл, и в то же время согласноoptionsПриходите и измените содержимое и суффикс соответствующего файла. Код слишком скучный, поэтому я не буду его публиковать.
plugin-xx
pluginЕсли это так, есть еще много вещей, которые можно сделать, я здесь только для того, чтобы изменить цепочкуwebpackинформация о конфигурации. Это только одна из функций, их гораздо больше, например: напишите самиwebpack plugin / loaderПроходить, делать что-то другое.
// example
module.exports = config => {
['.tsx', '.ts'].forEach(item => config.resolve.extensions.add(item));
config.module
.rule('js')
.test(/\.(js|ts|tsx|jsx)$/)
.use('babel-loader')
.tap(options => {
options.presets.push('@babel/preset-typescript');
return options;
});
};
github
PS: Начиная с сегодняшнего дня, продолжайте учиться. Еще недавно ничего не хотелось делать, каждый день после работы хотелось только лечь и растеряться.