├── .gitignore ├── package.json └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-cli", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "bin": "index.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "koa": "^2.13.1", 15 | "koa-send": "^5.0.1" 16 | }, 17 | "dependencies": { 18 | "@vue/compiler-sfc": "^3.0.5" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // 配置运行node 的位置 3 | const Koa = require('koa') 4 | const send = require('koa-send') 5 | const path = require('path') 6 | const { Readable } = require('stream') 7 | const compilerSFC = require('@vue/compiler-sfc') 8 | const app = new Koa() 9 | const streamToString = stream => new Promise((resolve, reject) => { 10 | const chunks = [] 11 | stream.on('data', chunk => chunks.push(chunk)) 12 | stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8'))) 13 | stream.on('error', reject) 14 | }) 15 | const stringToStream = text => { 16 | const stream = new Readable() 17 | stream.push(text) 18 | // 关闭流 19 | stream.push(null) 20 | return stream 21 | } 22 | // 3. 加载第三方模块 23 | app.use(async (ctx, next) => { 24 | // ctx.path --> /@modules/vue 25 | if (ctx.path.startsWith('/@modules/')) { 26 | const moduleName = ctx.path.substr(10) 27 | const pkgPath = path.join(process.cwd(), 'node_modules', moduleName, 'package.json') 28 | const pkg = require(pkgPath) 29 | ctx.path = path.join('/node_modules', moduleName, pkg.module) 30 | } 31 | await next() 32 | }) 33 | // 1. 静态文件服务器 34 | app.use(async (ctx, next) => { 35 | await send(ctx, ctx.path, { root: process.cwd(), index: 'index.html' }) 36 | await next() 37 | }) 38 | // 4. 处理单文件组件 39 | app.use(async (ctx, next) => { 40 | if (ctx.path.endsWith('.vue')) { 41 | const contents = await streamToString(ctx.body) 42 | const { descriptor } = compilerSFC.parse(contents) 43 | let code 44 | if (!ctx.query.type) { 45 | code = descriptor.script.content 46 | // console.log(code) 47 | code = code.replace(/export\s+default\s+/g, 'const __script = ') 48 | code += ` 49 | import { render as __render } from "${ctx.path}?type=template" 50 | __script.render = __render 51 | export default __script 52 | ` 53 | } else if (ctx.query.type === 'template') { 54 | const templateRender = compilerSFC.compileTemplate({ source: descriptor.template.content }) 55 | code = templateRender.code 56 | } 57 | ctx.type = 'application/javascript' 58 | ctx.body = stringToStream(code) 59 | } 60 | await next() 61 | }) 62 | // 2. 修改第三方模块的路径 63 | app.use(async (ctx, next) => { 64 | if (ctx.type === 'application/javascript') { 65 | const contents = await streamToString(ctx.body) 66 | // import vue from 'vue' 67 | // import App from './App.vue' 68 | ctx.body = contents 69 | .replace(/(from\s+['"])(?![\.\/])/g, '$1/@modules/') 70 | .replace(/process\.env\.NODE_ENV/g, '"development"') 71 | } 72 | }) 73 | app.listen(3000) 74 | console.log('Server running @ http://localhost:3000') --------------------------------------------------------------------------------