preface
Before writing the scaffolding of the base operation, behind to see the @vue/ CLI source code, have to say, is really very formal and standardized. Reference @vue/ CLI part of the function, to modify scaffolding.
Effect of chestnuts
The project structure
The structure of the project ChuhC-React is divided into four parts:
@chuhc/cli
Scaffolding command line content, initializing projects through commands, and so on.@chuhc/scripts
The project compiles and runs the package content, but has not been connected to the deployment process.@chuhc/template
Template file.@chuhc/plugin-xxx
Project-wrapped plug-ins, for example@chuhc/plugin-typescript
And so on.
cli
In the past, I directly obtained the information through some interactive commands of Inquirer, and then used the download-git-repo command to pull the template file corresponding to Github. Although this is relatively simple, it is difficult to maintain multiple templates.
In @vue/cli, multiple plug-ins are combined to form a monolithic template. In many scaffolding roots, an xxx.config.js is exposed. Then, when running the Node command, read the configuration file and perform corresponding operations according to the content of the configuration file, for example, dynamically modify the config using webpack-chain, and finally call toConfig to generate the new Webpack configuration content.
The plug-in determines and generates a PKG
A basic package.json template, in addition to the generic version, private, license, etc., such as name, scripts, dependencies, devDependencies need to be added manually.
Name uses scaffolding to initialize the parameters passed in, while scripts uses @chuhc/scripts to run commands after successfully introducing it.
For prerequisites such as react and react-dom, we can place them directly in dependencies, whereas devDependencies are plugins that the user selects manually during initialization.
Use Inquirer to list plug-ins and let users choose which plug-ins to introduce.
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, // A structure, what do you think is convenient, how to come},]);Copy the code
You can add the plugin to the json devDependencies file in the code above.
Get depends on the latest version
Both react and Plugin above require a version number. Here I use the command line to obtain the latest version and use it as its value. If you run execSync directly, it will block, and the ORA loading will be stuck, so select the promise to run, and terminate the promise by exec callback.
const forEachSetV = (list, obj, key) = > {
const promises = [];
const manager = hasCnpm() ? 'cnpm' : 'npm'; // Determine whether to select CNPM or 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);
Copy the code
Once you get the version number, fill in the json with other data as the package.json value and create it in the new project directory.
const fs = require('fs-extra'); // fs-extra is an extension of system FS module
const path = require('path');
module.exports = (dir, files) = > {
Object.keys(files).forEach(name= > {
const pathName = path.join(dir, name);
fs.ensureDirSync(path.dirname(pathName)); // Create a new folder if there is no folder
fs.writeFileSync(pathName, files[name]); // Create a new file
});
};
writeFileTree(targetDir, {
'package.json': JSON.stringify(pkg, null.2)});Copy the code
Select the package Management tool
Because the speed of NPM is not ideal, it can be used as a cushion. Check whether yarn or CNPM exists in the current environment. Select YARN or CNPM first. If neither exists, use NPM.
const PM_CONFIG = {
npm: {
install: ['install'.'--loglevel'.'error'].// Prints error information
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'; }}// encapsulates the run command function
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 means to pull the cache first, if not, to pull the server
} catch (e) {
this.runCommand('install'); // Report an error}}git() {
try {
execSync('git init');
return true;
} catch (e) {
return false; }}};Copy the code
To check whether the YARN and CNPM environments exist, you can check version to check whether the yarn and CNPM environments can be successfully executed. If the yarn and CNPM environments exist, the yarn and CNPM environments do not exist.
const judgeEnv = name= > {
const envKey = `_has${name[0].toUpperCase()}${name.slice(1)}`; // Save the result
if(_env[envKey] ! = =null) {
return _env[envKey];
}
try {
execSync(`${name} --version`, { stdio: 'ignore' }); // Do not print information
return (_env[envKey] = true);
} catch (e) {
return (_env[envKey] = false); }};const hasYarn = judgeEnv.bind(this.'yarn');
const hasCnpm = judgeEnv.bind(this.'cnpm');
Copy the code
Then install the dependencies using the install method and pass some parameters to @chuhc/template to copy some base templates.
scripts
Because it’s a multi-page project, scripts does the following:
- through
glob
To match the entry and then treat it asentry
Dynamically pass in, and dynamically pass in multiplehtml-webpack-plugin
toplugins
. - By reading the
chuhc.config.js
File, to dynamically modifywebpack
Configure the content and invoke the corresponding plug-in. - And then finally produce the final
webpack
Configuration file, passed towebpack
To compile, run, package, etc.
Match the entrance
The matching entry mainly uses glob to match, and only meets the matching requirements can it be used as an entry. Then, through the matching information, the corresponding entry content and plugin content are generated and passed to the Webpack configuration file.
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= > { // Pass through the 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= > { // Pass plugin
plugins.push(
new HtmlWebpackPlugin({
chunks: isProd ? ['libs', dirname] : [dirname],
filename: `. /${dirname}/index.html`.template: path.join(__dirname, './template/index.html')})); });return plugins;
};
Copy the code
Chain configuration config
Webpack-chain is recommended for chained configurations, as is @vue/ CLI. Because we already have some basic configuration content, we can merge our existing configuration objects into a configuration instance using config.merge.
However, direct conversion is not supported. We need to manually convert some configuration content, such as module. Plugins do not support plugins that are already new, so my solution is to skip the merge of plugins and finally merge config.toconfig () and plugins into the final configuration object using webpack-merge.
const Config = require('webpack-chain');
const chuhcConfig = require(`${process.cwd()}/chuhc.config.js`); // Read the configuration file in the root directory
const { setBaseConfig } = require('./util/merge'); // Merge existing profile objects into the configuration instance
const BASE = require('./config/webpack.base'); // Configure the base object
const DEVE = merge(BASE, require('./config/webpack.deve')); // Configure object deve
const PROD = merge(BASE, require('./config/webpack.prod')); // Configure object prod
const config = new Config();
// This is just an example of how plugins can be used to do many other things
const handleChuhcConfig = ({ plugins } = {}) = > {
// to do sth.
if (plugins) {
plugins.forEach(plugin= > {
require(plugin[0])(config, plugin[1]); }); }};const getConfig = isDeve= > {
config.clear(); // Clear the configuration
setBaseConfig(isDeve ? DEVE : PROD, config);
handleChuhcConfig(chuhcConfig);
return merge(config.toConfig(), {
plugins: isDeve ? DEVE.plugins : PROD.plugins
}); // Finally merge
};
Copy the code
Compile operation
After obtaining webpack Config, you can call the corresponding function, compile, run, pack and so on, depending on whether the dev command or build command is used. (Also, according to program.command)
/ run/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 = newWebpackDevServer(compiler, { ... devServer }); server.listen(devServer.port); };Copy the code
// 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) {
Error message received in callback.
console.error(err);
} else {
console.log(logSymbols.success, 'Packed successfully! '); }}); };Copy the code
template
Template is used to determine whether to copy the corresponding file based on the parameters passed in, and modify the corresponding file content and suffix according to options. The code is too boring to post.
plugin-xx
Plugin can do a lot of things, I just chain modify webpack configuration information. This is just one feature, there are many more such as: write your own Webpack plugin/loader to pass in, to do some other things.
// 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;
});
};
Copy the code
github
The complete code
PS: From today on, keep learning. A while ago, I didn’t want to do anything. Every day after work, I just wanted to lie down and be confused.