En medio de la noche, un fuerte tono de llamada me despertó. Rápidamente puse el teléfono en silencio y miré la pantalla. Era una llamada telefónica de una chica que llevé conmigo hace unos años. Mirando a mi esposa dormida y niño, caminé de puntillas hasta la cocina está al teléfono.
Tan pronto como se conectó, hubo un estallido de llanto: "Maestro, no puedo encontrar trabajo. ¡He estado buscando durante mucho tiempo!".
Me pregunté y pregunté: "Tienes 5 años de experiencia laboral front-end, ¿por qué no puedes encontrar trabajo?"
"Maestro, después de que usted se fue, me quedé en esta empresa. Todo lo que hago es agregar, eliminar, modificar y verificar negocios. No les interesa cuando escuchan esto durante la entrevista. No sé cómo presentar mi proyecto. experiencia."
En este punto, también entendí a grandes rasgos lo que estaba pasando, la consolé por un momento, le dije que pensara en una manera para ti más tarde y colgué el teléfono.
No pude conciliar el sueño durante mucho tiempo cuando volví a la cama, recordando que encontré el mismo problema cuando cambié de trabajo por primera vez. En ese momento, acababa de trabajar durante tres años y, al comienzo de la entrevista, también describí secamente algunas experiencias del proyecto sobre cómo agregar, eliminar, modificar y verificar. Después de varias entrevistas fallidas, comencé a hablar sobre la experiencia de desarrollo de algunas formas y tablas complejas, y tuve la suerte de cambiar de trabajo y obtener un aumento de salario.
Pero con 5 años de experiencia laboral, estos no son adecuados. Esta niña de 5 años debería mostrar las mejores prácticas para agregar, eliminar, modificar y verificar negocios en la entrevista, y el front-end de 5 años debería tener algo de front-end. capacidades de ingeniería.
Entonces, al día siguiente, llamé y le dije a mi hermana: "Primero resumes los 5 años de experiencia en desarrollo agregando, eliminando, modificando y verificando el negocio en una plantilla de desarrollo de mejores prácticas, y luego te enseñaré a escribir un andamio y coloque estas plantillas de desarrollo en el andamio. Otros proyectos utilizan estas plantillas a través del andamio para mejorar la eficiencia del desarrollo ".
La niña preguntó vacilante: "Andamio, es difícil de oír, ¿es bueno aprender?"
"No se preocupe, el scaffolding es muy simple. Scaffolding es en realidad una herramienta de línea de comandos (CLI) de Node.js escrita en js, que también está llena de código js, pero se ejecuta con Node.js y se utilizan algunas API de nodo. "Eso es todo, primero organiza las plantillas de mejores prácticas y yo te enseñaré a escribir el andamio y te enseñaré cómo hacerlo".
Que aprenderás
De hecho, no se ha implementado el andamiaje y la dificultad radica en la disposición y precipitación de las mejores prácticas. Este artículo no abordará el contenido de las mejores prácticas, solo le enseñará cómo implementar un andamio básico como soporte para demostrar las mejores prácticas.
Debido a que se trata de enseñar a las niñas a escribir andamios a mano, este artículo es muy detallado y tiene muchas palabras, unas 30.000 palabras, después de leerlo aprenderás:
Cómo construir un proyecto de andamio
Cómo desarrollar y depurar un andamio
Cómo recibir y procesar parámetros de comando en andamios
Cómo interactuar con los usuarios en andamios
Cómo copiar una carpeta o archivo en scaffolding
Cómo generar dinámicamente un archivo en scaffolding
Cómo afrontar los problemas de trayectoria en los andamios
Cómo instalar automáticamente dependencias de plantillas en scaffolding
Todo el código de este artículo se ha subido a GitHub [1] .
1. Construya un proyecto de andamio estilo monorepo
1.1 Cómo ejecutar js con Node.js
Para evitar problemas extraños causados por las diferencias de versión de Node.js, se recomienda instalar Node.js versión 16 o superior.
Simplemente busque un lugar para crear un archivo index.js y agregue el siguiente código al archivo:
console.log('Welcome to Mortal World');
Salida en la barra de direcciones del archivo cmd
Abra la ventana de la línea de comando, ingrese node index.js
el comando y presione Entrar para ejecutar el comando.
Puede ver la impresión en la ventana de línea de comando Welcome to Mortals World
.
1.2 Declara tus propios comandos
Si está familiarizado con Vue , debe tener cierta comprensión del andamio vue-cli , como ejecutar vue create myapp
comandos para crear un proyecto Vue .
Si no instalamos npm install \-g vue-cli
el scaffolding vue-cli y lo ejecutamos directamente en la ventana de línea de comando vue create myapp
, se informará un error, como se muestra en la siguiente figura:
Se puede ver vue
que no es un comando del sistema, vue
sino un comando declarado mediante el scaffolding vue-cli.
Entonces, cómo declarar un comando para andamios es realmente muy simple, sígueme para operar.
Encuentre un lugar para crear una carpeta mortal-cli , ingrese la carpeta, envíela a su barra de direcciones para cmd
abrir la ventana de línea de comando,
Ejecute el comando para inicializar un proyecto de scaffolding. Después de ejecutarlo correctamente, npm init
se generará un archivo packageage.json en esta carpeta.
Agregamos un campo en pakeage.jsonbin
para declarar un comando. El código agregado es el siguiente:
{
"name": "mortal-cli",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"bin":{
"mortal": "./bin/index.js"
}
}
De esta manera, hemos declarado un mortal
comando y ./bin/index.js
es mortal
la ruta relativa del archivo js que se ejecutará después de ejecutar el comando.
Luego creamos una carpeta bin en la carpeta mortal-cli , creamos un archivo index.js en la carpeta bin y agregamos el siguiente código al archivo:
js
copiar código
#!/usr/bin/env node console.log('Welcome to Mortal World');
Preste atención a agregarlo al principio del archivo
#!/usr/bin/env node
; de lo contrario, se informará un error después de ejecutarlo. ! !
De esta manera, se completa el proyecto de andamio más básico y luego ingresa mortal
el comando en la ventana de la línea de comando para ejecutar el comando.
Se encontrará que todavía se informa un error, lo que indica que mortal
el comando no es un comando del sistema y, por supuesto, no es una forma incorrecta de declarar el comando.
Suponga que publica este andamio en npm. Dado que el valor en mortal-cli/pakeage.json es, ejecutamos para instalar el andamio localmente y luego ejecutamos el comando, y descubriremos que la operación se realizó correctamente.name
mortal-cli
npm install \-g mortal-cli
mortal
Es imposible hacer esto en el desarrollo real de andamios, por lo que también necesitamos implementar la capacidad de depurar andamios localmente, que también es muy simple de implementar y se puede hacer con un solo comando.
Este comando es npm link
, jaja, no se me ocurre, así que no explicaré su principio aquí, si lo necesitas puedes dejar un mensaje y abriré un solo capítulo para explicarlo.
Después de ingresar npm link
el comando para ejecutar, ingrese mortal
el comando nuevamente, presione Entrar para ejecutar y el resultado que ve se muestra en la siguiente figura.
Hasta ahora, hemos declarado un mortal
comando con éxito.
1.3 Desventajas del enlace npm
Existe una desventaja de utilizarlo npm link
para la depuración local. Por ejemplo, existen varias versiones del almacén de andamios localmente. Después de modificar el código en el almacén A y ejecutar mortal
el comando, se descubre que el código modificado no surte efecto. Esto se debe a que ya se está ejecutando en el proyecto de andamiaje del almacén B npm link
, lo que hace mortal
que después de ejecutar el comando ejecutemos el código en el almacén B. Es extraño que el código modificado en el almacén A pueda surtir efecto. Después de ejecutar primero el proyecto de andamio en el almacén B npm unlink
y luego ejecutar el proyecto de andamio en el almacén A npm link
, el código modificado en el almacén A entrará en vigor.
Para resolver esta desventaja, utilizamos pnpm para construir proyectos de andamios estilo monorepo .
Un proyecto de estilo monorepo puede contener múltiples subproyectos, y cada subproyecto se puede compilar y empaquetar de forma independiente para enviar el producto en un paquete npm , por lo que monorepo también se denomina proyecto de paquetes múltiples.
Dado que el nombre del paquete del andamio lanzado a npm es mortal-cli , modifique el código en el archivo package.json del subproyecto de depuración . La parte de modificación del código es la siguiente:
{
"scripts": {
"mortal": "mortal --help"
},
"dependencies": {
"mortal-cli": "workspace:*"
}
}
Tenga en cuenta que la versión del paquete dependiente de mortal-clidependencies
declarada en el campo debe definirse por , no por un número de versión específico.workspace:*
Cuando se utiliza el protocolo workspace: en pnpm para definir un número de versión de un paquete dependiente, pnpm solo resolverá los paquetes dependientes en el espacio de trabajo y no descargará ni resolverá los paquetes dependientes en npm.
Introduzca el paquete de dependencia mortal-cli y ejecute pnpm i
la dependencia de instalación. El efecto es el mismo que la ejecución , excepto que no se instala globalmente. El andamiaje mortal-cli npm install \-g mortal-cli
solo se instala en el subproyecto de depuración . Luego, el subproyecto de depuración se refiere directamente al producto compilado y empaquetado localmente del proyecto de andamio, en lugar del producto lanzado a npm, para lograr completamente la depuración local.
Además, el proyecto de andamiaje y el subproyecto de depuración están en el mismo proyecto, por lo que se realiza una depuración uno a uno, resolviendo así las npm link
desventajas del uso para lograr la depuración local.
Al mismo tiempo, el comando del script se define en, el comando se ejecuta scripts
en el proyecto de depuración y el comando se puede ejecutar sin ejecutarse en el proyecto de andamio .pnpm mortal
mortal
npm link
mortal
1.4 Proyecto de andamio estilo monorepo
Comencemos a usar pnpm para construir un proyecto de andamio estilo monorepo . Primero, ingrese el siguiente código en la ventana de línea de comando para ejecutar la instalación de pnpm .
iwr https://get.pnpm.io/install.ps1 -useb | iex
Luego busque otro lugar para crear una carpeta mortal y, después de ingresar a la carpeta, escriba cmd en su barra de direcciones para abrir la ventana de línea de comando.
Ingrese pnpm init
al proyecto de inicialización, pnpm usa el espacio de trabajo (espacio de trabajo) para construir un proyecto de estilo monorepo .
Por lo tanto, debemos crear el archivo de configuración del espacio de trabajo pnpm-workspace.yaml en la carpeta mortal y agregar el siguiente código de configuración al archivo.
packages:
- 'packages/*'
- 'examples/*'
Después de la configuración, se declara que los subproyectos en las carpetas de paquetes y ejemplos pertenecen al mismo espacio de trabajo, y otros subproyectos pueden hacer referencia a los productos compilados y empaquetados de los subproyectos en el espacio de trabajo.
Cree una nueva carpeta mortal-cli en la carpeta de paquetes y envíela a su barra de direcciones para abrir una ventana de línea de comando.cmd
Ingrese el comando y ejecútelo para inicializar un proyecto. Después de una ejecución exitosa, pnpm init
se generará un archivo packageage.json en esta carpeta.
Agregamos el campo en pakeage.jsonbin
para declarar mortal
el comando, el código agregado es el siguiente:
{
"name": "mortal-cli",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"bin":{
"mortal": "./bin/index.js"
}
}
Cree una nueva carpeta bin en la carpeta paquetes/mortal-cli, cree un nuevo archivo index.js en la carpeta bin y agregue el siguiente código al archivo:
#!/usr/bin/env node
console.log('Welcome to Mortal World');
Cree una nueva carpeta de aplicación en la carpeta de ejemplos, ingrese cmd en su barra de direcciones para abrir la ventana de línea de comando y ejecute el comando para inicializar un proyecto. Después de ejecutarlo exitosamente, se generará un archivo packageage.json en la carpeta.pnpm init
Agregamos un campo en pakeage.jsondependencies
para agregar la dependencia de mortal-cli . Agregue scripts
otro comando de script personalizado a . El código agregado se ve así:
{
"name": "app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"mortal": "mortal"
},
"author": "",
"license": "ISC",
"dependencies": {
"mortal-cli": "workspace:*"
}
}
Luego ejecute el comando en el directorio raíz más externo pnpm i
para instalar las dependencias. Después de que la instalación sea exitosa, ejecútela en el directorio de la carpeta de la aplicación pnpm mortal
y encontrará que se imprime la ventana de línea de comando Welcome to Mortal World
, lo que indica que su proyecto de andamio estilo monorepo se ha creado con éxito.
En este punto, la estructura de directorios de todo el proyecto es la siguiente
|-- mortal
|-- package.json
|-- pnpm-lock.yaml
|-- pnpm-workspace.yaml
|-- examples
| |-- app
| |-- package.json
|-- packages
|-- mortal-cli
|-- package.json
|-- bin
|-- index.js
1.5 Módulos necesarios para andamios
Uno de los andamios más simples consta de los siguientes módulos.
módulo de parámetros de comando
módulo de interacción del usuario
módulo de copia de archivos
Módulo de generación dinámica de archivos.
Instalar automáticamente módulos dependientes
Implementémoslos uno por uno.
2. Módulo de parámetros de comando
2.1 Obtener parámetros de comando
El módulo de proceso en Node.js proporciona información del entorno global relacionada con el proceso actual de Node.js , como parámetros de comando, variables de entorno, ruta de ejecución de comandos, etc.
const process = require('process');
// 获取命令参数
console.log(process.argv);
Los parámetros también se pueden configurar después de los comandos proporcionados por scaffolding mortal
. Los parámetros de comando de scaffolding estándar deben admitir dos formatos, por ejemplo:
mortal --name=orderPage
mortal --name orderPage
Si se process.argv
obtiene a través de, es inconveniente tratar adicionalmente con dos formatos de parámetros de comando diferentes.
Aquí se recomienda la biblioteca de código abierto yargs para analizar los parámetros del comando. Ejecute el siguiente comando para instalar yargs :
pnpm add yargs --F mortal-cli
pnpm add
es el comando para instalar paquetes dependientes en pnpm--F mortal-cli
y especifica las dependencias que se instalarán en el subproyecto mortal-cli .
Cabe señalar aquí que
mortal-cli
se toma el valor del campo en package.json en el subproyecto mortal-cli en lugar del nombre de la carpeta del subproyecto mortal-cli .name
El uso de yargs es muy simple y el atributo que proporciona argv
es el resultado del procesamiento de los parámetros del comando en dos formatos.
Agregue el siguiente código en bin/index.js :
#!/usr/bin/env node
const yargs = require('yargs');
console.log('name', yargs.argv.name);
Tenga en cuenta que el código anterior se ejecuta en el entorno Node.js y los módulos de Node.js siguen la especificación CommonJS. Si desea depender de un módulo, debe utilizar la función del sistema integrada de Node.js para hacer referencia al módulo
require
.
Ejecutar en el directorio de la carpeta de la aplicaciónpnpm mortal \-- \--name=orderPage
,
Tenga en cuenta que
pnpm mortal
debe agregar dos guiones (--) después de , lo cual indica a pnpm que los siguientes parámetros se pasan al comandomortal
en sí, no apnpm
.
El resultado se muestra en la siguiente figura:
yargs.argv.name
El valor del parámetro del comando se puede obtener mediante name
.
2.2 Establecer subcomandos
Si el andamio necesita proporcionar múltiples funciones externas, no todas las funciones se pueden mortal
implementar en el comando.
Puede configurar algunos subcomandos mediante el método proporcionado por yargs , de modo que cada subcomando corresponda a su propia función y realice sus tareas.command
yargs.command
El uso es **yargs.command(cmd, desc, builder, handler)**.
cmd
: también se puede pasar una cadena, el nombre del subcomando y una matriz, por ejemplo['create', 'c']
, significa que se llama al subcomandocreate
y su alias esc
;desc
: Cadena, información de descripción del subcomando;builder
: Una función que devuelve una matriz, configuración de información de parámetros de subcomando, por ejemplo, se pueden configurar parámetros:alias
: alias;demand
: ¿Es necesario?default
:Valores predeterminados;describe
:Descripción;type
: tipo de parámetro,string | boolean | number
.
handler
: función, el parámetro del subcomando se puede procesar especialmente en esta función.
Configuremos un subcomando para generar una plantilla y nombremos este subcomando como create
.
Modifique el código en el archivo bin/index.js de la siguiente manera:
#!/usr/bin/env node
const yargs = require('yargs');
yargs.command(
['create', 'c'],
'新建一个模板',
function (yargs) {
return yargs.option('name', {
alias: 'n',
demand: true,
describe: '模板名称',
type: 'string'
})
},
function (argv) {
console.log('argv', argv);
}
).argv;
Ejecute los comandos y respectivamente en el directorio de la carpeta de la aplicación y los resultados de la ejecución se muestran en la siguiente figura:pnpm mortal create \-- \--name=orderPage
pnpm mortal c \-- \--name=orderPage
En lo anterior configuramos cierta información de parámetros de create
los parámetros del subcomando. name
¿Cómo mostrárselos al usuario? De hecho, siempre que los parámetros del subcomando que ingresamos sean incorrectos, la información del parámetro se mostrará en la ventana de la línea de comando.
Ejecute el comando en el directorio de la carpeta de la aplicación pnpm mortal c \-- \--abc
y el resultado de la ejecución se muestra en la siguiente figura:
Hasta ahora, hemos logrado la interacción más simple entre andamios y usuarios, pero si hay demasiados parámetros personalizados, el método de interacción de los parámetros de la línea de comandos es muy hostil para los usuarios. Por lo tanto, también necesitamos implementar un módulo de interacción del usuario. Consulte la siguiente sección para saber cómo implementarlo.
3. Módulo de interacción del usuario
Creo que un mejor método de interacción con el usuario es la interacción interrogativa. Por ejemplo, estamos ejecutando y el contenido del archivo package.jsonnpm init
se completa mediante la interacción interrogativa .
Se recomienda utilizar la biblioteca de código abierto de Investigar para lograr la interacción interrogativa. Ejecute el siguiente comando para instalar Investigar :
js
copiar código
pnpm add [email protected] --F mortal-cli
Para utilizar Inquirerrequire
, se debe utilizar la versión 8.2.5 de Inquirer .
Aquí utilizamos principalmente las capacidades de tres aspectos de la biblioteca de código abierto del investigador :
hacer preguntas a los usuarios
Obtener y analizar la entrada del usuario
Comprueba si la respuesta del usuario es válida.
Se logra principalmente inquirer.prompt()
a través de . prompt
La función recibe una matriz y cada elemento de la matriz es un elemento de consulta. El elemento de consulta tiene muchos parámetros de configuración. Los siguientes son elementos de configuración de uso común.
type
: Los tipos de preguntas que se utilizan comúnmente soncuadro de entrada:
input
;Confirmar:
confirm
;Grupo de radio:
list
;Grupo de opción múltiple:
checkbox
;
name
: Una variable que almacena la respuesta a la pregunta actual;message
: Descripción del problema;default
:Valores predeterminados;choices
: opción de lista,type
disponible en algunos;validate
: Verificar la respuesta del usuario;filter
: filtra la respuesta del usuario y devuelve el valor procesado.
Por ejemplo, cuando creamos un archivo de plantilla, probablemente le preguntaremos al usuario: nombre del archivo de plantilla, tipo de plantilla, qué marco usar para el desarrollo, qué biblioteca de componentes usar para el marco a desarrollar, etc. Implementemos esta función a continuación.
Cree una nueva carpeta requester.js en la carpeta bin y agregue el siguiente código en ella:
const inquirer = require('inquirer');
function inquirerPrompt(argv) {
const { name } = argv;
return new Promise((resolve, reject) => {
inquirer.prompt([
{
type: 'input',
name: 'name',
message: '模板名称',
default: name,
validate: function (val) {
if (!/^[a-zA-Z]+$/.test(val)) {
return "模板名称只能含有英文";
}
if (!/^[A-Z]/.test(val)) {
return "模板名称首字母必须大写"
}
return true;
},
},
{
type: 'list',
name: 'type',
message: '模板类型',
choices: ['表单', '动态表单', '嵌套表单'],
filter: function (value) {
return {
'表单': "form",
'动态表单': "dynamicForm",
'嵌套表单': "nestedForm",
}[value];
},
},
{
type: 'list',
message: '使用什么框架开发',
choices: ['react', 'vue'],
name: 'frame',
}
]).then(answers => {
const { frame } = answers;
if (frame === 'react') {
inquirer.prompt([
{
type: 'list',
message: '使用什么UI组件库开发',
choices: [
'Ant Design',
],
name: 'library',
}
]).then(answers1 => {
resolve({
...answers,
...answers1,
})
}).catch(error => {
reject(error)
})
}
if (frame === 'vue') {
inquirer.prompt([
{
type: 'list',
message: '使用什么UI组件库开发',
choices: [ 'Element'],
name: 'library',
}
]).then(answers2 => {
resolve({
...answers,
...answers2,
})
}).catch(error => {
reject(error)
})
}
}).catch(error => {
reject(error)
})
})
}
exports.inquirerPrompt = inquirerPrompt;
Entre ellos inquirer.prompt()
, devuelve una Promesa, que podemos usar then
para obtener la respuesta a la consulta anterior y luego iniciar el contenido correspondiente de acuerdo con la respuesta.
Introducido en bin /index.jsinquirerPrompt
.
#!/usr/bin/env node
const yargs = require('yargs');
const { inquirerPrompt } = require("./inquirer");
yargs.command(
['create', 'c'],
'新建一个模板',
function (yargs) {
return yargs.option('name', {
alias: 'n',
demand: true,
describe: '模板名称',
type: 'string'
})
},
function (argv) {
inquirerPrompt(argv).then(answers =>{
console.log(answers)
})
}
).argv;
Ejecute el comando en el directorio de la carpeta de la aplicación pnpm mortal c \-- \--n Input
y el resultado de la ejecución se muestra en la siguiente figura:
Se puede ver claramente que la respuesta a la pregunta de "qué marco se usa para el desarrollo" es diferente, y las opciones para la siguiente pregunta de "qué biblioteca de componentes de UI usar para el desarrollo" son diferentes.
Una vez completada la respuesta, el formato de la respuesta se puede ver claramente en la imagen a continuación.
4. Módulo de copia de carpetas
Para generar un archivo de plantilla, la forma más sencilla es copiar el archivo de plantilla en el andamio al lugar correspondiente después de ejecutar el comando proporcionado por el andamio. Un archivo de plantilla puede ser un solo archivo o una carpeta. Esta sección presenta primero cómo copiar el archivo de plantilla cuando es una carpeta.
Copiar carpetas en Node.js no es fácil y se requiere recursividad. Aquí, se recomienda utilizar la biblioteca de código abierto copy - dir para copiar archivos.
Ejecute el siguiente comando para instalar copy-dir .
pnpm add copy-dir --F mortal-cli
Cree un nuevo archivo copy.js en la carpeta bin y agregue el siguiente código:
const copydir = require('copy-dir');
const fs = require('fs');
function copyDir(from, to, options) {
copydir.sync(from, to, options);
}
function checkMkdirExists(path) {
return fs.existsSync(path)
};
exports.checkMkdirExists = checkMkdirExists;
exports.copyDir = copyDir;
copyDir
La implementación del método es muy simple, pero la dificultad es cómo usarlo, creemos una escena para presentar cómo usarlo.
Creamos una nueva carpeta de plantilla en la carpeta bin para almacenar archivos de plantilla. Por ejemplo, creamos una carpeta de formulario en la carpeta de plantilla para almacenar plantillas de formulario. El contenido de la plantilla de formulario no se presenta aquí. Creamos una en la carpeta de formulario en will._index.js_, simplemente escribe algo en él. Su estructura de directorios es la siguiente:
|-- mortal
|-- package.json
|-- pnpm-lock.yaml
|-- pnpm-workspace.yaml
|-- examples
| |-- app
| |-- package.json
|-- packages
|-- mortal
|-- package.json
|-- bin
|-- template
|-- form
|-- index.js
|-- copy.js
|-- index.js
|-- inquirer.js
A continuación, copie la carpeta paquetes/mortal/bin/template/form a ejemplos/app/src/pages/OrderPage .
Modifique el código en bin/index.js , el código modificado es el siguiente:
#!/usr/bin/env node
const yargs = require('yargs');
const path = require('path');
const { inquirerPrompt } = require("./inquirer");
const { copyDir, checkMkdirExists } = require("./copy");
yargs.command(
['create', 'c'],
'新建一个模板',
function (yargs) {
return yargs.option('name', {
alias: 'n',
demand: true,
describe: '模板名称',
type: 'string'
})
},
function (argv) {
inquirerPrompt(argv).then(answers => {
const { name, type } = answers;
const isMkdirExists = checkMkdirExists(
path.resolve(process.cwd(),`./src/pages/${name}`)
);
if (isMkdirExists) {
console.log(`${name}文件夹已经存在`)
} else {
copyDir(
path.resolve(__dirname, `./template/${type}`),
path.resolve(process.cwd(), `./src/pages/${name}`)
)
}
})
}
).argv;
La dificultad de utilizar el método de copiar archivo copyDir
es la asignación de parámetros from
y to
. Entre ellos from
, representa la ruta del archivo que se copiará y to
representa la ruta donde se copiará el archivo. La ruta aquí es mejor usar una ruta absoluta, porque surgirán una serie de problemas extraños al usar una ruta relativa en Node.js.
4.1 Procesamiento de caminos en andamios
Podemos usar el método proporcionado por el módulo de ruta en Node.js para convertir la ruta en una ruta absoluta, que consiste en unir los parámetros en una ruta absoluta. Es un elemento opcional y se pueden establecer varias rutas. Por ejemplo , Las reglas de empalme de rutas a las que se debe prestar atención al usarpath.resolve( [from…], to )
to
[from … ]
path.resolve('./aaa', './bbb', './ccc')
path.resolve
Coser caminos de atrás hacia adelante;
Si
to
comienza/
con, no se unirá a la ruta anterior;Si
to
comienza../
con , empalme las rutas anteriores y no incluya la última ruta;Si
to
comienza./
con o no tiene símbolo, se concatenará la ruta anterior.
De las reglas de empalme anteriores, path.resolve
cuando se utiliza, se debe prestar especial atención to
a la configuración del parámetro.
Introduzcamos copyDir
cómo configurar los parámetros al usar el método:
Establezca
copyDir
el parámetro de en ,from
path.resolve(__dirname, `./template/${type}`)
Entre ellos __dirname
se encuentra la ruta absoluta utilizada para obtener dinámicamente el directorio al que pertenece el módulo de archivo actual. Por ejemplo, si se usa en el archivo bin/index.js__dirname
, __dirname
significa la ruta absoluta del directorio al que pertenece el archivo bin/index.jsD:\mortal\packages\mortal-cli\bin
.
Debido a que el archivo de plantilla se almacena en la carpeta bin/template y copyDir
se usa en bin/index.js , la ruta de la carpeta _bin/template_ es relativa al archivo bin/index.js./template
, así que establezca path.resolve
el parámetro to
en ./template/${type}
, ¿dónde type
está el usuario seleccionado El tipo de plantilla.
Suponiendo type
que el tipo de plantilla es form
, entonces path.resolve(__dirname, `./template/form`)
la ruta absoluta obtenida por es D:\mortal\packages\mortal-cli\bin\template\form
.
Establezca
copyDir
el parámetro de en ,to
path.resolve(process.cwd(), `${name}`)
Entre ellos, la ruta absoluta del directorio al que pertenece el archivo cuando se ejecuta process.cwd()
el proceso actual de Node.js. Por ejemplo, cuando se ejecuta en el directorio de la carpeta binnode index.js
, process.cwd()
lo que obtiene es D:\mortal\packages\mortal-cli\bin
.
Ejecute node index.js
el mortal
comando en lugar de ejecutar. En la ingeniería front-end moderna, los comandos de script se definen en el archivo package.json , de la siguiente manera:scripts
{
"name": "app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"mortal": "mortal"
},
"author": "",
"license": "ISC",
"dependencies": {
"mortal-cli": "workspace:*"
}
}
Ejecutar pnpm mortal
es equivalente a ejecutar mortal
el comando, por lo que pnpm mortal
al ejecutar, el archivo cuando se ejecuta el proceso actual de Node.js es el archivo package.json . Bueno, process.cwd()
consíguelo D:\mortal\examples\app
.
Debido a que la carpeta paquetes/mortal/bin/template/form debe copiarse en ejemplos/app/src/pages/OrderPage y process.cwd()
el valor de es , la ruta de D:\mortal\examples\app
la carpeta _src/pages_ relativa a ejemplos/aplicación./src/pages
es , por lo que path.resolve
parámetro de to
Establecer en ./src/pages/${name}
, donde name
está el nombre de la plantilla ingresado por el usuario.
4.2 Guardia de directorio
Ejecútelo en el directorio de la carpeta de la aplicación pnpm mortal create \-- \--name=OrderPage
para ver si puede copiar correctamente la carpeta paquetes/mortal/bin/template/form a ejemplos/app/src/pages/OrderPage .
Se informó un error que indica que la carpeta ejemplos/app/src/pages no existe. Para evitar tales errores, necesitamos implementar un método de protección de directorio mkdirGuard
. Por ejemplo, si la carpeta ejemplos/app/src/pages no existe, cree una carpeta ejemplos/app/src/pages .
En el archivo bin/copy.js , modifique el código de la siguiente manera:
const copydir = require('copy-dir');
const fs = require('fs');
const path = require('path');
function mkdirGuard(target) {
try {
fs.mkdirSync(target, { recursive: true });
} catch (e) {
mkdirp(target)
function mkdirp(dir) {
if (fs.existsSync(dir)) { return true }
const dirname = path.dirname(dir);
mkdirp(dirname);
fs.mkdirSync(dir);
}
}
}
function copyDir(form, to, options) {
mkdirGuard(to);
copydir.sync(form, to, options);
}
function checkMkdirExists(path) {
return fs.existsSync(path)
};
exports.checkMkdirExists = checkMkdirExists;
exports.mkdirGuard = mkdirGuard;
exports.copyDir = copyDir;
fs.mkdirSync
El formato de sintaxis: fs.mkdirSync(path[, options])
, para crear un directorio de carpetas.
path
: ruta del directorio de la carpeta;options
:recursive
indica si se debe crear un directorio principal,true
sí.
fs.existsSync
El formato de sintaxis es:, fs.existsSync(pach)
verifique si el directorio existe, regrese si el directorio existe true
y regrese si el directorio no existe false
.
path
: Ruta del directorio de carpetas.
path.dirname
El formato de sintaxis de: path.dirname(path)
se utiliza para obtener el nombre del directorio de la ruta dada.
path
:ruta de archivo.
Dentro del método, cuando el directorio principal del mkdirGuard
directorio que se va a crear no existe, la llamada informará un error y seguirá parte de la lógica, en la que el directorio principal se crea de forma recursiva y el directorio principal se utiliza para determinar si el directorio principal se crea de forma recursiva. El directorio principal existe para finalizar la recursividad. Aquí se debe prestar especial atención a la creación de un directorio principal antes de llamar para formar una secuencia de creación correcta; de lo contrario, el proceso de creación de un directorio principal informará un error porque el directorio principal del directorio principal no existe.target
fs.mkdirSync(target)
catch
fs.existsSync(dir)
fs.mkdirSync(dir)
mkdirp(dirname)
Ejecutemos nuevamente en el directorio de la carpeta de la aplicación pnpm mortal create \-- \--name=OrderPage
para ver si podemos copiar con éxito la carpeta paquetes/mortal/bin/template/form a ejemplos/app/src/pages/OrderPage esta vez .
Agregado exitosamente, el resultado agregado es el siguiente:
Luego ejecute pnpm mortal create \-- \--name=OrderPage
el comando nuevamente y encontrará que la consola imprime que la plantilla ya existe y le pregunta.
Esto es para evitar que el archivo de plantilla modificado por el usuario se sobrescriba al estado inicial después de ejecutar el comando. checkMkdirExists
Entonces introducimos un método para verificar si el archivo de plantilla existe , que se implementa internamente fs.existsSync
.
5. Módulo de copia de archivos
La copia de archivos se implementa en tres pasos: use para fs.readFileSync
leer el contenido del archivo copiado, luego cree un archivo y luego use para fs.writeFileSync
escribir el contenido del archivo.
En el archivo bin/copy.js , agregue el siguiente código dentro:
function copyFile(from, to) {
const buffer = fs.readFileSync(from);
const parentPath = path.dirname(to);
mkdirGuard(parentPath)
fs.writeFileSync(to, buffer);
}
exports.copyFile = copyFile;
A continuación, utilizamos copyFile
el método para modificar el código en bin/index.js , el código modificado es el siguiente:
#!/usr/bin/env node
const yargs = require('yargs');
const path = require('path');
const { inquirerPrompt } = require("./inquirer");
const { copyDir } = require("./copy");
yargs.command(
['create', 'c'],
'新建一个模板',
function (yargs) {
return yargs.option('name', {
alias: 'n',
demand: true,
describe: '模板名称',
type: 'string'
})
},
function (argv) {
inquirerPrompt(argv).then(answers => {
const { name, type } = answers;
const isMkdirExists = checkMkdirExists(
path.resolve(process.cwd(),`./src/pages/${name}/index.js`)
);
if (isMkdirExists) {
console.log(`${name}/index.js文件已经存在`)
} else {
copyFile(
path.resolve(__dirname, `./template/${type}/index.js`),
path.resolve(process.cwd(), `./src/pages/${name}/index.js`),
{
name,
}
)
}
})
}
).argv;
copyFile
La diferencia entre usar y copyDir
usar está en los parámetros, copyFile
que requieren que tanto los parámetros from
como los parámetros to
sean precisos en la ruta del archivo.
Ejecútelo en el directorio de la carpeta de la aplicación pnpm mortal create \-- \--name=OrderPage
y el resultado de la ejecución se muestra en la siguiente figura:
6. Módulo de generación dinámica de archivos.
Supongamos que parte de la información en el archivo de plantilla proporcionado en el andamio necesita generar dinámicamente el archivo de plantilla correspondiente de acuerdo con los parámetros de comando ingresados por el usuario.
Por ejemplo, en el archivo de plantilla siguiente , ¿cómo App
reemplazar dinámicamente el valor del parámetro de comando ingresado por el usuario ?name
import React from 'react';
const App = () => {
return (
<div></div>
);
};
export default App;
Se recomienda utilizar la biblioteca de código abierto bigote para implementarlo, ejecute el siguiente comando para instalar copy-dir .
pnpm add mustache --F mortal-cli
Creamos un archivo index.tpl en la carpeta paquetes/mortal-cli/bin/template/form con el siguiente contenido:
import React from 'react';
const {
{name}} = () => {
return (
<div></div>
);
};
export default {
{name}};
Primero escriba un readTemplate
método para leer el contenido del archivo de plantilla dinámica index.tpl . En el archivo bin/copy.js , agregue el siguiente código dentro:
const Mustache = require('mustache');
function readTemplate(path, data = {}) {
const str = fs.readFileSync(path, { encoding: 'utf8' })
return Mustache.render(str, data);
}
exports.readTemplate = readTemplate;
readTemplate
El método recibe dos parámetros, path
la ruta relativa del archivo de plantilla dinámica y data
los datos de configuración del archivo de plantilla dinámica.
Úselo para Mustache.render(str, data)
generar el contenido del archivo de plantilla y devolverlo, debido a que Mustache.render
el primer tipo de parámetro es una cadena, por lo que al llamar, fs.readFileSync
especifique encoding
el tipo como utf8
; de lo contrario fs.readFileSync
, devuelva datos de tipo búfer.
Escribir un copyTemplate
método para copiar el archivo de plantilla en el lugar correspondiente es copyFile
muy similar al método. En el archivo bin/copy.js , agregue el siguiente código dentro:
function copyTemplate(from, to, data = {}) {
if (path.extname(from) !== '.tpl') {
return copyFile(from, to);
}
const parentToPath = path.dirname(to);
mkdirGuard(parentToPath);
fs.writeFileSync(to, readTemplate(from, data));
}
path.extname(from)
Devuelve la extensión del archivo, por ejemplo, path.extname(index.tpl)
return .tpl
.
Modifique el código en bin/index.js , el código modificado es el siguiente:
#!/usr/bin/env node
const yargs = require('yargs');
const path = require('path');
const { inquirerPrompt } = require("./inquirer");
const { copyTemplate } = require("./copy");
yargs.command(
['create', 'c'],
'新建一个模板',
function (yargs) {
return yargs.option('name', {
alias: 'n',
demand: true,
describe: '模板名称',
type: 'string'
})
},
function (argv) {
inquirerPrompt(argv).then(answers => {
const { name, type } = answers;
const isMkdirExists = checkMkdirExists(
path.resolve(process.cwd(),`./src/pages/${name}/index.js`)
);
if (isMkdirExists) {
console.log(`${name}/index.js文件已经存在`)
} else {
copyTemplate(
path.resolve(__dirname, `./template/${type}/index.tpl`),
path.resolve(process.cwd(), `./src/pages/${name}/index.js`),
{
name,
}
)
}
})
}
).argv;
Ejecútelo en el directorio de la carpeta de la aplicación pnpm mortal create \-- \--name=OrderPage
y el resultado de la ejecución se muestra en la siguiente figura:
6.1 Introducción al bigote
El caso anterior es el uso más simple de bigote . A continuación se presentan algunos escenarios de uso comunes.
Primero, familiaricémonos con la gramática del bigote , introduzcamos algunos escenarios para usar estas gramáticas.
{ {clave}}
{ {#clave}} { {/clave}}
{ {^clave}} { {/clave}}
{ {.}}
{ {&clave}}
6.1.1, enlace simple
Utilice {
{key}}
la sintaxis, key
que debe ser Mustache.render
coherente con el nombre de propiedad del segundo parámetro (un objeto) del método.
Por ejemplo:
Mustache.render('<span>{
{name}}</span>',{name:'张三'})
producción:
<span>张三</span>
6.1.2 Subpropiedades vinculantes
Por ejemplo:
Mustache.render('<span>{
{ifno.name}}</span>', { ifno: { name: '张三' } })
producción:
<span>张三</span>
6.1.3, renderizado en bucle
Si key
el valor del atributo es una matriz, puede usar {
{#key}} {
{/key}}
la sintaxis para mostrarlo en un bucle. La {
{#}}
marca indica que todo el contenido después de esta marca se mostrará en un bucle y {
{/}}
la marca indica el final del ciclo.
Por ejemplo:
Mustache.render(
'<span>{
{#list}}{
{name}}{
{/list}}</span>',
{
list: [
{ name: '张三' },
{ name: '李四' },
{ name: '王五' },
]
}
)
producción:
<span>张三李四王五</span>
Si list
el valor de es ['张三','李四','王五']
, debe {
{name}}
reemplazarlo {
{.}}
por para renderizar.
Mustache.render(
'<span>{
{#list}}{
{.}}{
{/list}}</span>',
{
list: ['张三','李四','王五']
}
)
6.1.4 Procesamiento de datos secundarios en el bucle
Mustache.render
El segundo parámetro en el método es un objeto y su valor de atributo puede ser una función. Al renderizar, la función se ejecutará para generar el valor de retorno. En la función, puede usarlo para obtener el contexto del segundo parámetro this
.
Por ejemplo:
Mustache.render(
'<span>{
{#list}}{
{info}}{
{/list}}</span>',
{
list: [
{ name: '张三' },
{ name: '李四' },
{ name: '王五' },
],
info() {
return this.name + ',';
}
}
)
producción:
<span>张三,李四,王五,</span>
6.1.5, renderizado condicional
Utilice {
{#key}} {
{/key}}
sintaxis y {
{^key}} {
{/key}}
sintaxis para lograr la representación condicional. Cuando key
es false
,,,,, y es verdadero, el contenido empaquetado no se 0
representa y el contenido empaquetado sí se representa.[]
{}
null
key == false
{
{#key}} {
{/key}}
{
{^key}} {
{/key}}
Por ejemplo:
Mustache.render(
'<span>{
{#show}}显示{
{/show}}{
{^show}}隐藏{
{/show}}</span>',
{
show: false
}
)
producción:
<span>隐藏</span>
6.1.6 No escapar de las etiquetas HTML
Utilice {
{&key}}
la sintaxis para hacerlo.
Por ejemplo:
Mustache.render(
'<span>{
{&key}}</span>',
{
key: '<span>标题</span>'
}
)
producción:
<span><span>标题</span></span>
7. Instalar automáticamente módulos dependientes
Supongamos que la plantilla es así:
import React from 'react';
import { Button, Form, Input } from 'antd';
const App = () => {
const onFinish = (values) => {
console.log('Success:', values);
};
return (
<Form onFinish={onFinish} autoComplete="off">
<Form.Item label="Username" name="username">
<Input />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">提交</Button>
</Form.Item>
</Form>
);
};
export default App;
Puede ver que las dos dependencias de terceros react
se utilizan en la plantilla . Si estas dos dependencias no están instaladas en el proyecto usando la plantilla, debemos realizar la instalación automática de estas dos dependencias durante el proceso de generación de la plantilla.antd
Usamos el módulo de subproceso child_process en Node para lograr esto.
La sintaxis más común en child_process es :
child_process.exec(command, options, callback)
command
: comando, comopnpm install
options
:parámetrocwd
: establece la ruta del entorno de ejecución del comandoenv
:Variable ambientaltimeout
:ejecutar ejecutar ahora
callback
: La devolución de llamada al final de la ejecución del comando,(error, stdout, stderr) =>{ }
después de una ejecución exitosaerror
esnull
y después de una ejecución fallidaerror
es una instancia de error,stdout
esstderr
una salida estándar, un error estándar y su formato es una cadena de forma predeterminada.
Cree una nueva carpeta requester.js en la carpeta bin y agregue el siguiente código en ella:
const path = require('path');
const { exec } = require('child_process');
const LibraryMap = {
'Ant Design': 'antd',
'iView': 'view-ui-plus',
'Ant Design Vue': 'ant-design-vue',
'Element': 'element-plus',
}
function install(cmdPath, options) {
const { frame, library } = options;
const command = `pnpm add ${frame} && pnpm add ${LibraryMap[library]}`
return new Promise(function (resolve, reject) {
exec(
command,
{
cwd: path.resolve(cmdPath),
},
function (error, stdout, stderr) {
console.log('error', error);
console.log('stdout', stdout);
console.log('stderr', stderr)
}
)
})
}
exports.install = install;
El parámetro en install
el método es el comando de dependencia de instalación pnpm y el empalme se utiliza al instalar múltiples dependencias. El parámetro es la ruta del archivo package.json del proyecto dependiente instalado , que podemos usar para obtenerlo. Como se mencionó anteriormente, es la ruta absoluta del directorio al que pertenece el archivo cuando se ejecuta el proceso actual de Node.js.exec
command
&&
cwd
process.cwd()
process.cwd()
El próximo uso, modifique el código en bin/index.js , el código modificado es el siguiente:
#!/usr/bin/env node
const yargs = require('yargs');
const path = require('path');
const { inquirerPrompt } = require("./inquirer");
const { copyTemplate, checkMkdirExists } = require("./copy");
const { install } = require('./manager');
yargs.command(
['create', 'c'],
'新建一个模板',
function (yargs) {
return yargs.option('name', {
alias: 'n',
demand: true,
describe: '模板名称',
type: 'string'
})
},
function (argv) {
inquirerPrompt(argv).then(answers => {
const { name, type } = answers;
const isMkdirExists = checkMkdirExists(
path.resolve(process.cwd(),`./src/pages/${name}/index.js`)
);
if (isMkdirExists) {
console.log(`${name}/index.js文件已经存在`)
} else {
copyTemplate(
path.resolve(__dirname, `./template/${type}/index.tpl`),
path.resolve(process.cwd(), `./src/pages/${name}/index.js`),
{
name,
}
)
install(process.cwd(), answers);
}
})
}
).argv;
Después de ejecutar el método, copyTemplate
se ejecutarán install(process.cwd(), answers)
las dependencias requeridas en la plantilla de instalación automática .
Ejecútelo en el directorio de la carpeta de la aplicación pnpm mortal create \-- \--name=OrderPage
para ver si las dependencias se pueden instalar automáticamente.
Después de ejecutar el comando, verifique si las dependencias y se agregan al valor en el archivo ejemplos\app\package.json .dependencies
antd
react
Además, cuando ejecutamos el comando, encontraremos que, como se muestra en la figura siguiente, el cursor sigue parpadeando, como si estuviera atascado, y las dependencias se están instalando. Aquí vamos a presentar una animación de carga para resolver este fenómeno hostil.
Se recomienda utilizar la biblioteca de código abierto ora para realizar la animación de carga.
Ejecute el siguiente comando para instalar ora .
pnpm add [email protected] --F mortal-cli
Modifique el código en bin/inquirer.js , el código modificado es el siguiente:
const path = require('path');
const { exec } = require('child_process');
const ora = require("ora");
const LibraryMap = {
'Ant Design': 'antd',
'iView': 'view-ui-plus',
'Ant Design Vue': 'ant-design-vue',
'Element': 'element-plus',
}
function install(cmdPath, options) {
const { frame, library } = options;
const command = `pnpm add ${frame} && pnpm add ${LibraryMap[library]}`
return new Promise(function (resolve, reject) {
const spinner = ora();
spinner.start(
`正在安装依赖,请稍等`
);
exec(
command,
{
cwd: path.resolve(cmdPath),
},
function (error) {
if (error) {
reject();
spinner.fail(`依赖安装失败`);
return;
}
spinner.succeed(`依赖安装成功`);
resolve()
}
)
})
}
exports.install = install;
Ejecútelo en el directorio de la carpeta de la aplicación pnpm mortal create \-- \--name=OrderPage
para ver el efecto de ejecución.
8. Publicar e instalar
Ejecute en el directorio de la carpeta paquetes/mortal , ejecute el siguiente comando para instalar y publicar el scaffolding en npm .
js
copiar código
pnpm publish --F mortal-cli
Después de un lanzamiento exitoso. En cualquier proyecto, podemos ejecutar el comando en el proyecto después de pnpm add mortal-cli \-D
instalar correctamente las dependencias de scaffolding de mortal-clipnpm mortal create \-- \--name=OrderPage
.
epílogo
Lo anterior sólo le enseña a realizar el andamio más simple. Su función sólo la genera un archivo de plantilla. Aunque simples, estas son las habilidades introductorias del andamio. El código se ha subido a GitHub [2] , puedes descargarlo y practicarlo tú mismo. Nunca lo aprenderás si no lo practicas.
Después de aprender, podrá resumir algunos códigos comerciales habituales, formular las mejores prácticas y utilizar andamios como soporte para mostrarlos y mejorar su competitividad en el lugar de trabajo.
Referencias
[1]
https://github.com/532pyh/mortal-cli: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2F532pyh%2Fmortal-cli
[2]https://github.com/532pyh/mortal-cli: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2F532pyh%2Fmortal-cli
Acerca de este artículo
Autor: Hongchen Lianxin
https://juejin.cn/post/7260144602471776311
Beneficios para los fanáticos
Comparta el código fuente de una plantilla de fondo de NodeJs basada en Tailwind Css. Si desea aprender sobre nodejs y tailwindcss, no se pierda este código fuente.