npm i lerna -g
lerna init
命令 | 功能 |
---|---|
lerna bootstrap | 安装依赖 |
lerna clean | 删除各个包下的node_modules |
lerna init | 创建新的lerna库 |
lerna list | 查看本地包列表 |
lerna changed | 显示自上次release tag以来有修改的包, 选项通 list |
lerna diff | 显示自上次release tag以来有修改的包的差异, 执行 git diff |
lerna exec | 在每个包目录下执行任意命令 |
lerna run | 执行每个包package.json中的脚本命令 |
lerna add | 添加一个包的版本为各个包的依赖 |
lerna import | 引入package |
lerna link | 链接互相引用的库 |
lerna create | 新建package |
lerna publish | 发布 |
{
"name": "root",
"private": true,
"devDependencies": {
"lerna": "^4.0.0"
}
}
{
"packages": [
"packages/*"
],
"version": "0.0.0"
}
node_modules
.DS_Store
design
*.log
packages/test
dist
temp
.vuerc
.version
.versions
.changelog
package.json
{
"name": "root",
"private": true,
+ "workspaces": [
+ "packages/*"
+ ],
"devDependencies": {
"lerna": "^4.0.0"
}
}
lerna.json
{
"packages": [
"packages/*"
],
"version": "1.0.0",
+ "useWorkspaces": true,
+ "npmClient": "yarn"
}
设置加速镜像
yarn config set registry http://registry.npm.taobao.org
npm config set registry https://registry.npm.taobao.org
作用 | 命令 |
---|---|
查看工作空间信息 | yarn workspaces info |
给根空间添加依赖 | yarn add chalk cross-spawn fs-extra --ignore-workspace-root-check |
给某个项目添加依赖 | yarn workspace create-react-app3 add commander |
删除所有的 node_modules | lerna clean 等于 yarn workspaces run clean |
安装和link | yarn install 等于 lerna bootstrap --npm-client yarn --use-workspaces |
重新获取所有的 node_modules | yarn install --force |
查看缓存目录 | yarn cache dir |
清除本地缓存 | yarn cache clean |
lerna create vite-cli
lerna create vite-project
{
"name": "vite-cli",
"version": "0.0.0",
"bin":{
"vite-cli":"./bin/vite.js"
},
"scripts": {}
}
packages\vite-cli\bin\vite.js
#!/usr/bin/env node
function start() {
require('../lib/cli')
}
start()
packages\vite-cli\lib\cli.js
console.log('vite');
{
"name": "vite-project",
"version": "0.0.0",
"scripts": {}
}
yarn
cd packages/vite-cli
npm link
npm root -g
vite-cli
cd packages/vite-project
yarn workspace vite-project add vite
cd packages/vite-cli
yarn workspace vite-cli add es-module-lexer koa koa-static magic-string chalk dedent hash-sum
packages\vite-project\package.json
{
"name": "vite-project",
"version": "0.0.0",
+ "scripts": {
+ "dev":"vite"
+ },
"dependencies": {
"vite": "^2.4.1"
}
}
packages\vite-project\index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
packages\vite-project\src\main.js
console.log('main.js');
.vscode\launch.json
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "vue-cli",
"cwd":"${workspaceFolder}/packages/vite-project",
"runtimeExecutable": "npm",
"runtimeArgs": [
"run",
"dev"
],
"port":9229,
"autoAttachChildProcesses": true,
"stopOnEntry": true,
"skipFiles": [
"<node_internals>/**"
]
}
]
}
packages\vite-cli\lib\serveStaticPlugin.js
const path = require('path');
const static = require('koa-static');
//这个插件相当于给应用添加了一个静态文件中间件,以前的命令选择目录为根目录
function serveStaticPlugin({app,projectRoot}){
app.use(static(projectRoot));
}
module.exports = serveStaticPlugin;
packages\vite-cli\lib\cli.js
const Koa = require('koa');
const serveStaticPlugin = require('./serveStaticPlugin');
function createServer() {
const app = new Koa();
const root = process.cwd();
// 构建上下文对象
const context = {
app,
root
}
app.use((ctx, next) => {
// 扩展ctx属性
Object.assign(ctx, context);
return next();
});
const resolvedPlugins = [
serveStaticPlugin
];
// 依次注册所有插件
resolvedPlugins.forEach(plugin => plugin(context));
return app;
}
createServer().listen(4000);
yarn workspace vite-project add vue@3 @vitejs/plugin-vue @vue/compiler-sfc
node ./node_modules/esbuild/install.js
packages\vite-project\nodemon.json
{
"watch":["../vite-cli"]
}
启动服务
nodemon ../vite-cli/bin/vite.js
packages\vite-project\vite.config.js
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
export default defineConfig({
plugins: [vue({})],
});
packages\vite-project\src\main.js
+import {createApp} from 'vue';
+console.log(createApp);
packages\vite-cli\lib\cli.js
const Koa = require('koa');
const dedent = require('dedent');
const serveStaticPlugin = require('./serveStaticPlugin');
+const moduleRewritePlugin = require('./moduleRewritePlugin');
function createServer() {
//koa的实例
const app = new Koa();
//当前命令所在的根目录
const root = process.cwd();
//上下文
const context = {
app,
root
}
app.use((ctx, next) => {
Object.assign(ctx, context);
return next();
});
const resolvedPlugins = [
+ moduleRewritePlugin,
serveStaticPlugin
]
resolvedPlugins.forEach(plugin => plugin(context));
return app;
}
createServer().listen(4000, async () => {
const chalk = await import('chalk');
console.log(
dedent`${chalk.default.green(`vite-cli dev server running at:`)}
> Local: http://localhost:4000/
`
);
});
packages\vite-cli\lib\serverPluginModuleRewrite.js
let { readBody } = require('./utils');
let MagicString = require('magic-string');
let { parse } = require('es-module-lexer');
let path = require('path');
async function rewriteImports(content) {
var magicString = new MagicString(content);
let imports = await parse(content);
if (imports && imports.length > 0) {
for (let i = 0; i < imports[0].length; i++) {
const { n, s, e } = imports[0][i];
//如果开头既不是/也不是.的话才会需要替换
if (/^[^\/\.]/.test(n)) {
const rewriteModuleId = `/node_modules/.vite/${n}.js`;
magicString.overwrite(s, e, rewriteModuleId);
}
}
}
return magicString.toString();
}
function moduleRewritePlugin({ root, app }) {
app.use(async (ctx, next) => {
await next();//一上来就next了 next之前神马都没有
//如果有响应体,并且此响应体的内容类型是js mime-type=application/javascript
if (ctx.body && ctx.response.is('js')) {
const content = await readBody(ctx.body);
const result = await rewriteImports(content);
ctx.body = result;
}
});
}
module.exports = moduleRewritePlugin;
packages\vite-cli\lib\utils.js
const { Readable } = require('stream')
async function readBody(stream) {
if (stream instanceof Readable) {
return new Promise((resolve) => {
let buffers = [];
stream
.on('data', (chunk) => buffers.push(chunk))
.on('end', () => resolve(Buffer.concat(buffers).toString()));
})
} else {
return stream.toString()
}
}
exports.readBody = readBody
packages\vite-cli\lib\moduleResolvePlugin.js
const fs = require('fs').promises;
const node_modulesRegexp = /^\/node_modules\/\.vite\/(.+?)\.js/
const { resolveVue } = require('./utils')
function moduleResolvePlugin({ app, root }) {
const vueResolved = resolveVue(root)
app.use(async (ctx, next) => {
if (!node_modulesRegexp.test(ctx.path)) {
return next();
}
const id = ctx.path.match(node_modulesRegexp)[1];
const modulePath = vueResolved[moduleId];
//如果vite预构建
//const modulePath = path.join(root, ctx.path)
const content = await fs.readFile(modulePath, 'utf8');
ctx.type = 'js';
ctx.body = content
});
}
module.exports = moduleResolvePlugin;
packages\vite-cli\lib\injectProcessPlugin.js
const { readBody } = require("./utils");
function injectProcessPlugin({ root, app }) {
const devInjection = `
<script>
window.process = {env:{NODE_ENV:'development'}}
</script>
`
app.use(async (ctx, next) => {
await next();
if (ctx.response.is('html')) {
const html = await readBody(ctx.body);
ctx.body = html.replace(/<head>/, `$&${devInjection}`)
}
})
}
module.exports = injectProcessPlugin
packages\vite-cli\lib\utils.js
const { Readable } = require('stream');
const Module = require('module')
async function readBody(stream) {
if(stream instanceof Readable){
return new Promise((resolve) => {
let buffers = [];
//当我们从流中读取到数据后
stream
.on('data',chunk=>buffers.push(chunk))
.on('end',()=>resolve(Buffer.concat(buffers).toString('utf8')))
});
}else{
return Promise.resolve(stream.toString('utf8'));
}
}
exports.readBody = readBody;
+function resolveVue(root) {
+ let require = Module.createRequire(root);
+ const resolvePath = (moduleName) => require.resolve(`@vue/${moduleName}/dist/${moduleName}.esm-bundler.+js`);
+ return {
+ '@vue/shared': resolvePath('shared'),
+ '@vue/reactivity': resolvePath('reactivity'),
+ '@vue/runtime-core': resolvePath('runtime-core'),
+ 'vue': resolvePath('runtime-dom'),
+ }
+}
+exports.resolveVue = resolveVue;
packages\vite-cli\lib\cli.js
const Koa = require('koa');
const dedent = require('dedent');
const serveStaticPlugin = require('./serveStaticPlugin');
const moduleRewritePlugin = require('./moduleRewritePlugin');
+const moduleResolvePlugin = require('./moduleResolvePlugin');
+const injectProcessPlugin = require('./injectProcessPlugin');
function createServer() {
//koa的实例
const app = new Koa();
//当前命令所在的根目录
const root = process.cwd();
//上下文
const context = {
app,
root
}
app.use((ctx, next) => {
Object.assign(ctx, context);
return next();
});
const resolvedPlugins = [
+ injectProcessPlugin,
moduleRewritePlugin,
+ moduleResolvePlugin,
serveStaticPlugin
]
resolvedPlugins.forEach(plugin => plugin(context));
return app;
}
createServer().listen(4000, async () => {
const chalk = await import('chalk');
console.log(
dedent`${chalk.default.green(`vite-cli dev server running at:`)}
> Local: http://localhost:4000/
`
);
});
packages\vite-project\src\main.js
import {createApp} from 'vue';
+import App from './App.vue';
+createApp(App).mount("#app");
packages\vite-project\src\App.vue
<template>
<h1>App</h1>
</template>
<script>
export default {
name:'App'
}
</script>
packages\vite-cli\lib\vuePlugin.js
const fs = require('fs').promises;
const path = require('path');
const hash = require('hash-sum')
const { parse, compileScript, compileTemplate, rewriteDefault } = require('@vue/compiler-sfc');
var cache = new Map();
function vuePlugin({ root, app }) {
app.use(async (ctx, next) => {
if (!ctx.path.endsWith('.vue')) {
return await next();
}
const filePath = path.join(root, ctx.path);
const descriptor = await getDescriptor(filePath, root);
let targetCode = ``;
//js
if (descriptor.script) {
let script = compileScript(descriptor, { reactivityTransform: false });
scriptCode = rewriteDefault(script.content, '_sfc_main')
targetCode += scriptCode;
}
//template
if (descriptor.template) {
let templateContent = descriptor.template.content;
const { code: templateCode } = compileTemplate({ source: templateContent });
targetCode += templateCode;
}
targetCode += `\n_sfc_main.render=render`;
targetCode += `\nexport default _sfc_main`;
ctx.type = 'js';
ctx.body = targetCode;
});
}
async function getDescriptor(filePath) {
if (descriptorCache.has(filePath)) {
return descriptorCache.get(filePath);
}
const content = await fs.readFile(filePath, 'utf8');
const { descriptor } = parse(content, { filename: filePath });
descriptorCache.set(filePath, descriptor);
return descriptor;
}
module.exports = vuePlugin;
const { parse, compileScript, compileTemplate, rewriteDefault } = require('@vue/compiler-sfc');
const dedent = require('dedent');//indent dedent
const App = `
<template>
<h1>App</h1>
</template>
<script >
export default {
name: 'App'
}
</script>
<style>
h1 {
color: red;
}
</style>
<style>
h1 {
background-color: green;
}
</style>
`;
let { descriptor } = parse(App, { filename: 'App.vue' });
let targetCode = '';
//console.log(descriptor);
//import "/src/App.vue?t=1647142419693&vue&type=style&index=0&lang.css"
if (descriptor.styles.length > 0) {
let styleCodes = '';
descriptor.styles.forEach((style, index) => {
const query = `?t=${Date.now()}&vue&type=style&index=${index}&lang.css`;
const id = '/src/App.vue';
const styleRequest = id + query;
styleCodes += `\nimport ${JSON.stringify(styleRequest)}`
});
targetCode += styleCodes;
}
if (descriptor.script) {
let scriptCode = compileScript(descriptor, {
reactivityTransform
: false
});
scriptCode = rewriteDefault(scriptCode.content, '_sfc_main');
targetCode += scriptCode;
}
if (descriptor.template) {
const templateContent = descriptor.template.content;
let { code } = compileTemplate({
source: templateContent
});
code = code.replace(/export function render/, 'function _sfc_render');
targetCode += code;
}
targetCode += `
\n_sfc_main.render = _sfc_render;
\nexport default _sfc_main;
`;
console.log(targetCode);
packages\vite-cli\lib\cli.js
const Koa = require('koa');
const dedent = require('dedent');
const serveStaticPlugin = require('./serveStaticPlugin');
const moduleRewritePlugin = require('./moduleRewritePlugin');
const moduleResolvePlugin = require('./moduleResolvePlugin');
const injectProcessPlugin = require('./injectProcessPlugin');
+const vuePlugin = require('./vuePlugin')
function createServer() {
//koa的实例
const app = new Koa();
//当前命令所在的根目录
const root = process.cwd();
//上下文
const context = {
app,
root
}
app.use((ctx, next) => {
Object.assign(ctx, context);
return next();
});
const resolvedPlugins = [
injectProcessPlugin,
moduleRewritePlugin,
moduleResolvePlugin,
+ vuePlugin,
serveStaticPlugin
]
resolvedPlugins.forEach(plugin => plugin(context));
return app;
}
createServer().listen(4000, async () => {
const chalk = await import('chalk');
console.log(
dedent`${chalk.default.green(`vite-cli dev server running at:`)}
> Local: http://localhost:4000/
`
);
});
packages\vite-cli\lib\vuePlugin.js
const fs = require('fs').promises;
const path = require('path');
const hash = require('hash-sum')
const { parse, compileScript, compileTemplate, rewriteDefault, compileStyleAsync } = require('@vue/compiler-sfc');
var descriptorCache = new Map();
function vuePlugin({ root, app }) {
app.use(async (ctx, next) => {
if (!ctx.path.endsWith('.vue')) {
return await next();
}
const filePath = path.join(root, ctx.path);
const descriptor = await getDescriptor(filePath, root);
if (ctx.query.type === 'style') {
const block = descriptor.styles[Number(ctx.query.index)];
let result = await transformStyle(block.content, descriptor, ctx.query.index);
ctx.type = 'js';
ctx.body = `
let style = document.createElement('style');
style.innerHTML = ${JSON.stringify(result.code)};
document.head.appendChild(style);
`;
} else {
let targetCode = ``;
if (descriptor.styles.length) {
let stylesCode = '';
descriptor.styles.forEach((style, index) => {
const query = `?vue&type=style&index=${index}&lang.css`
const id =ctx.path;
const styleRequest = (id + query).replace(/\\/g, '/');
stylesCode += `\nimport ${JSON.stringify(styleRequest)}`
});
targetCode += stylesCode;
}
//js
if (descriptor.script) {
let script = compileScript(descriptor, { id: filePath, reactivityTransform: false });
scriptCode = rewriteDefault(script.content, '_sfc_main')
targetCode += scriptCode;
}
//template
if (descriptor.template) {
let templateContent = descriptor.template.content;
let { code } = compileTemplate({ id: filePath, source: templateContent });
code = code.replace(/export function render/, 'function _sfc_render');
targetCode += code;
}
targetCode += `\n_sfc_main.render=_sfc_render`;
targetCode += `\nexport default _sfc_main`;
ctx.type = 'js';
ctx.body = targetCode;
}
});
}
async function transformStyle(code, descriptor, index) {
const block = descriptor.styles[index];
const result = await compileStyleAsync({
filename: descriptor.filename,
source: code,
id: `data-v-${descriptor.id}`,
scoped: block.scoped
})
return result;
}
async function getDescriptor(filePath) {
if (descriptorCache.has(filePath)) {
return descriptorCache.get(filePath);
}
const content = await fs.readFile(filePath, 'utf8');
const { descriptor } = parse(content, { filename: filePath });
descriptorCache.set(filePath, descriptor);
return descriptor;
}
module.exports = vuePlugin;