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
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\serverPluginServeStatic.js
const path = require('path');
function serveStaticPlugin({ app, root }) {
    // 以当前根目录作为静态目录
    app.use(require('koa-static')(root));
    // 以public目录作为根目录
    app.use(require('koa-static')(path.join(root, 'public')))
}
exports.serveStaticPlugin = serveStaticPlugin;
packages\vite-cli\lib\cli.js
const Koa = require('koa');
const {serveStaticPlugin} = require('./serverPluginServeStatic');
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 {serveStaticPlugin} = require('./serverPluginServeStatic');
+const {moduleRewritePlugin} = require('./serverPluginModuleRewrite');
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 = [
+       moduleRewritePlugin,
        serveStaticPlugin
    ];
    // 依次注册所有插件
    resolvedPlugins.forEach(plugin => plugin(context));
    return app;
}
createServer().listen(4000,()=>{
+   console.log(`dev server running at:  http://localhost:4000/`);
});
packages\vite-cli\lib\serverPluginModuleRewrite.js
magic-string是一个操作字符串和生成source-map的工具
var MagicString = require('magic-string');
var magicString = new MagicString('export var name = "zhufeng"');
//返回magicString的克隆,删除原始字符串开头和结尾字符之前的所有内容
console.log(magicString.snip(0, 6).toString());
//从开始到结束删除字符(原始字符串,而不是生成的字符串)
console.log(magicString.remove(0,7).toString());
//使用MagicString.Bundle可以联合多个源代码
let bundleString = new MagicString.Bundle();
bundleString.addSource({
  content: 'var a = 1;',
  separator: '\n'
})
bundleString.addSource({
  content: 'var b = 2;',
  separator: '\n'
})
console.log(bundleString.toString());
const { readBody } = require("./utils");
const { parse } = require('es-module-lexer');
const MagicString = require('magic-string');
function rewriteImports(source) {
    let imports = parse(source)[0];
    const magicString = new MagicString(source);
    if (imports.length) {
        for (let i = 0; i < imports.length; i++) {
            const { s, e } = imports[i];
            let id = source.substring(s, e);//vue
            if (/^[^\/\.]/.test(id)) {
                id = `/@modules/${id}`;// /node_modules/.vite/vue.js?v=14157793
                magicString.overwrite(s, e, id);
            }
        }
    }
    return magicString.toString();
}
function moduleRewritePlugin({ app, root }) {
    app.use(async (ctx, next) => {
        await next();
        console.log(ctx.body);
        // 对类型是js的文件进行拦截
        if (ctx.body && ctx.response.is('js')) {
            // 读取文件中的内容
            const content = await readBody(ctx.body);
            // 重写import中无法识别的路径
            const result = rewriteImports(content);
            ctx.body = result;
        }
    });
}
exports.moduleRewritePlugin = 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\serverPluginModuleResolve.js
const fs = require('fs').promises;
const moduleRE = /^\/@modules\//;
const { resolveVue } = require('./utils')
function moduleResolvePlugin({ app, root }) {
    const vueResolved = resolveVue(root)
    app.use(async (ctx, next) => {
        // 对 /@modules 开头的路径进行映射
        if (!moduleRE.test(ctx.path)) {
            return next();
        }
        // 去掉 /@modules/路径
        const id = ctx.path.replace(moduleRE, '');
        ctx.type = 'js';
        const content = await fs.readFile(vueResolved[id], 'utf8');
        ctx.body = content
    });
}
exports.moduleResolvePlugin = moduleResolvePlugin;
packages\vite-cli\lib\serverPluginHtml.js
const { readBody } = require("./utils");
function htmlRewritePlugin({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}`)
        }
    })
}
exports.htmlRewritePlugin = htmlRewritePlugin
packages\vite-cli\lib\utils.js
const { Readable } = require('stream')
const path = require('path');
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
function resolveVue(root) {
    const compilerPkgPath = path.resolve(root, '../../node_modules', '@vue/compiler-sfc/package.json');
    const compilerPkg = require(compilerPkgPath);
    // 编译模块的路径  node中编译
    const compilerPath = path.join(path.dirname(compilerPkgPath), compilerPkg.main);
    const resolvePath = (name) => path.resolve(root, '../../node_modules', `@vue/${name}/dist/${name}.esm-bundler.js`);
    // dom运行
    const runtimeDomPath = resolvePath('runtime-dom')
    // 核心运行
    const runtimeCorePath = resolvePath('runtime-core')
    // 响应式模块
    const reactivityPath = resolvePath('reactivity')
    // 共享模块
    const sharedPath = resolvePath('shared')
    return {
        vue: runtimeDomPath,
        '@vue/runtime-dom': runtimeDomPath,
        '@vue/runtime-core': runtimeCorePath,
        '@vue/reactivity': reactivityPath,
        '@vue/shared': sharedPath,
        compiler: compilerPath,
    }
}
exports.resolveVue=resolveVue;
packages\vite-cli\lib\cli.js
const Koa = require('koa');
const {serveStaticPlugin} = require('./serverPluginServeStatic');
const {moduleRewritePlugin} = require('./serverPluginModuleRewrite');
const {moduleResolvePlugin} = require('./serverPluginModuleResolve');
const {htmlRewritePlugin} = require('./serverPluginHtml');
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 = [
        htmlRewritePlugin,
        moduleRewritePlugin,
        moduleResolvePlugin,
        serveStaticPlugin
    ];
    // 依次注册所有插件
    resolvedPlugins.forEach(plugin => plugin(context));
    return app;
}
createServer().listen(4000,()=>{
    console.log(`dev server running at:  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\serverPluginVue.js
const path = require('path');
const fs = require('fs').promises;
const { resolveVue } = require('./utils');
const defaultExportRE = /((?:^|\n|;)\s*)export default/
function serverPluginVue({ app, root }) {
    app.use(async (ctx, next) => {
        if (!ctx.path.endsWith('.vue')) {
            return next();
        }
        // vue文件处理
        const filePath = path.join(root, ctx.path);
        const content = await fs.readFile(filePath, 'utf8');
        // 获取文件内容
        let { parse, compileTemplate } = require(resolveVue(root).compiler);
        let { descriptor } = parse(content); // 解析文件内容
        if (!ctx.query.type) {
            let code = ``;
            if (descriptor.script) {
                let content = descriptor.script.content;
                let replaced = content.replace(defaultExportRE, '$1const __script =');
                code += replaced;
            }
            if (descriptor.template) {
                const templateRequest = ctx.path + `?type=template`
                code += `\nimport { render as __render } from ${JSON.stringify(
                    templateRequest
                )}`;
                code += `\n__script.render = __render`
            }
            ctx.type = 'js'
            if (descriptor.script)
            code += `\nexport default __script`;
            ctx.body = code;
        }
        if (ctx.query.type == 'template') {
            ctx.type = 'js';
            let content = descriptor.template.content;
            const { code } = compileTemplate({ source: content });
            ctx.body = code;
        }
    })
}
exports.serverPluginVue = serverPluginVue;
packages\vite-cli\lib\cli.js
const Koa = require('koa');
const {serveStaticPlugin} = require('./serverPluginServeStatic');
const {moduleRewritePlugin} = require('./serverPluginModuleRewrite');
const {moduleResolvePlugin} = require('./serverPluginModuleResolve');
const {htmlRewritePlugin} = require('./serverPluginHtml');
+const {serverPluginVue} = require('./serverPluginVue')
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 = [
        htmlRewritePlugin,
        moduleRewritePlugin,
        moduleResolvePlugin,
+       serverPluginVue,
        serveStaticPlugin
    ];
    // 依次注册所有插件
    resolvedPlugins.forEach(plugin => plugin(context));
    return app;
}
createServer().listen(4000,()=>{
    console.log(`dev server running at:  http://localhost:4000/`);
});