1. 创建项目 #

1278ae09f1de08186e9e70fe3bb1c4d1

1.1 Lerna #

1.1.1 安装 #

npm i lerna -g

1.1.2 初始化 #

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 发布

1.1.3 文件 #

1.1.3.1 package.json #
{
  "name": "root",
  "private": true,
  "devDependencies": {
    "lerna": "^4.0.0"
  }
}
1.1.3.2 lerna.json #
{
  "packages": [
    "packages/*"
  ],
  "version": "0.0.0"
}
1.1.3.3 .gitignore #
node_modules
.DS_Store
design
*.log
packages/test
dist
temp
.vuerc
.version
.versions
.changelog

1.1.4 yarn workspace #

1.1.4.1 package.json #

package.json

{
  "name": "root",
  "private": true,
+  "workspaces": [
+    "packages/*"
+  ],
  "devDependencies": {
    "lerna": "^4.0.0"
  }
}
1.1.4.2 lerna.json #

lerna.json

{
  "packages": [
    "packages/*"
  ],
  "version": "1.0.0",
+ "useWorkspaces": true,
+ "npmClient": "yarn"
}
1.1.4.3 添加依赖 #

设置加速镜像

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

1.1.5 创建子项目 #

lerna create vite-cli
lerna create  vite-project
1.1.5.1 vite-cli #
1.1.5.1.1 package.json #
{
  "name": "vite-cli",
  "version": "0.0.0",
  "bin":{
    "vite-cli":"./bin/vite.js"
  },
  "scripts": {}
}
1.1.5.1.2 vite.js #

packages\vite-cli\bin\vite.js

#!/usr/bin/env node
function start() {
    require('../lib/cli')
}
start()
1.1.5.1.3 cli.js #

packages\vite-cli\lib\cli.js

console.log('vite');
1.1.5.2 vite-project #
1.1.5.2.1 package.json #
{
  "name": "vite-project",
  "version": "0.0.0",
  "scripts": {}
}

1.1.6 创建软链接 #

yarn
cd packages/vite-cli
npm link
npm root -g
vite-cli

1.2 安装依赖 #

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

2. 启动并调试 #

2.1 package.json #

packages\vite-project\package.json

{
  "name": "vite-project",
  "version": "0.0.0",
+ "scripts": {
+   "dev":"vite"
+ },
  "dependencies": {
    "vite": "^2.4.1"
  }
}

2.2 index.html #

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>

2.3 src\main.js #

packages\vite-project\src\main.js

console.log('main.js');

2.4 launch.json #

.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>/**"
            ]
        }
    ]
}

3. 实现静态服务 #

3.1 serverPluginServeStatic.js #

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;

3.2 cli.js #

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);

4.重写导入路径 #

4.1 安装 #

yarn workspace vite-project add  vue@3  @vitejs/plugin-vue @vue/compiler-sfc
node ./node_modules/esbuild/install.js

4.2 nodemon.json #

packages\vite-project\nodemon.json

{
    "watch":["../vite-cli"]
}
nodemon ../vite-cli/bin/vite.js

4.3 vite.config.js #

packages\vite-project\vite.config.js

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
export default defineConfig({
  plugins: [vue({})],
});

4.4 main.js #

packages\vite-project\src\main.js

+import {createApp} from 'vue';
+console.log(createApp);

4.5 cli.js #

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/`);
});

4.6 serverPluginModuleRewrite.js #

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;

4.7 utils.js #

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

5.解析/@modules文件 #

5.1 serverPluginModuleResolve.js #

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;

5.2 serverPluginHtml.js #

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

5.3 utils.js #

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;

5.4 cli.js #

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/`);
});

6.编译vue模板 #

6.1 main.js #

packages\vite-project\src\main.js

import {createApp} from 'vue';
+import App from './App.vue';
+createApp(App).mount("#app");

6.2 App.vue #

packages\vite-project\src\App.vue

<template>
    <h1>App</h1>
</template>
<script>
export default {
    name:'App'
}
</script>

6.3 serverPluginVue.js #

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;

6.4 cli.js #

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/`);
});