Initial commit

This commit is contained in:
natuka committed 2025-06-05 09:25:26 +08:00
commit 84061c9a18
243 files changed
+63148

No files matched your search

+127
View File
@@ -0,0 +1,127 @@
const { join } = require('path')
const url = require('url')
const chokidar = require('chokidar')
const signale = require('signale')
const { match } = require('path-to-regexp')
const { initMock, getMatchMock } = require('./getMockData')
const { winPath } = require('./utils')
const { configBabelRegister } = require('./registerBabel')
let watcher
function getPaths(cwd) {
const winCwd = winPath(cwd)
const absMockPath = winPath(join(winCwd, 'mock'))
return {
absMockPath,
}
}
function parseJson(req) {
return new Promise((resolve) => {
let body = ''
let jsonStr = ''
req.on('data', (chunk) => {
body += chunk
})
req.on('end', () => {
try {
jsonStr = JSON.parse(body)
}
catch (err) {
jsonStr = ''
}
resolve(jsonStr)
})
})
}
const defaultOptions = {
watch: true,
cwd: process.cwd(), // 配置需要mock的url
mockUrlList: [],
// 是否开启mock
enable: true,
}
function initWatchMockFiles(paths) {
// chokidar 在 windows 下使用反斜杠组成的 glob 无法正确 watch 文件变动
// ref: https://github.com/paulmillr/chokidar/issues/777
const absPagesGlobPath = winPath(join('./mock', '**/*.[jt]s'))
watcher = chokidar.watch([...['./mock'], absPagesGlobPath], {
ignoreInitial: true,
})
watcher.on('all', (event, file) => {
signale.info(`[${event}] ${file}, reload mock data`)
cleanRequireCache(paths)
})
process.once('SIGINT', () => {
watcher.close()
})
}
function cleanRequireCache(paths) {
Object.keys(require.cache).forEach((file) => {
if (
paths.some((path) => {
return winPath(file).includes(path)
})
)
delete require.cache[file]
})
}
module.exports = function (opts) {
const options = Object.assign({}, defaultOptions, opts)
const { absMockPath } = getPaths(options.cwd)
const paths = [absMockPath]
if (options.enable) {
// vite热更新时关闭上一个进程
if (watcher)
watcher.close()
configBabelRegister(paths, {
cwd: options.cwd,
})
initMock(options)
if (options.watch)
initWatchMockFiles(paths)
}
return {
name: 'vite:mock',
enforce: 'pre',
configureServer({ middlewares }) {
if (!options.enable)
return
const middleware = async (req, res, next) => {
const matchMock = getMatchMock(req.url)
if (matchMock) {
let queryParams = {}
if (req.url)
// eslint-disable-next-line n/no-deprecated-api
queryParams = url.parse(req.url, true)
const reqUrl = queryParams.pathname
let query = queryParams.query
if (reqUrl) {
const isGet = req.method && req.method.toUpperCase() === 'GET'
if ((isGet && JSON.stringify(query) === '{}') || !isGet) {
const urlMatch = match(url, { decode: decodeURIComponent })
const params = urlMatch(reqUrl).params
if (JSON.stringify(params) !== '{}')
query = urlMatch(reqUrl).params || {}
else
query = queryParams.query || {}
}
}
const body = await parseJson(req)
res.setHeader('Content-Type', 'application/json;charset=utf-8')
res.send = opt => res.end(JSON.stringify(opt))
res.json = opt => res.end(JSON.stringify(opt))
matchMock.handler({ url: req.url, body, query, headers: req.headers }, res)
return
}
next()
}
middlewares.use(middleware)
},
}
}
+108
View File
@@ -0,0 +1,108 @@
const path = require('path')
const glob = require('glob')
const { pathToRegexp } = require('path-to-regexp')
const signale = require('signale')
const { winPath } = require('./utils')
let _mockconfig = {}
const cwd = process.cwd()
let mockList = []
// 获取mock文件列表
function getMockFile() {
let mockFiles = glob
.sync('mock/**/*.[jt]s', {
cwd,
ignore: [],
})
.map(p => path.join(cwd, p))
// windows下路径处理
mockFiles = mockFiles.map(p => winPath(p))
return mockFiles
}
// 获取mock文件数据
function getMockData(files) {
let mockData = {}
files.forEach((mockFile) => {
try {
const m = require(mockFile)
mockData = Object.assign(mockData, m.default || m)
return mockData
}
catch (e) {
console.error(e)
throw e
}
})
return mockData
}
// mock数据格式化
function parseMockData(config) {
mockList = Object.keys(config).reduce((list, key) => {
const handler = config[key]
const { method, path } = parseMockUrl(key)
const keys = []
const re = pathToRegexp(path, keys)
list.push({
method,
path,
re,
keys,
handler: mockHandle(handler),
})
return list
}, [])
}
function getMatchMock(url) {
return (
_mockconfig.mockUrlList.findIndex((whiteUrl) => {
if (Object.prototype.toString.call(whiteUrl) === '[object RegExp]')
return whiteUrl.test(url)
else
return url.includes(whiteUrl)
}) !== -1 && mockList.filter(item => url.includes(item.path)).sort((a, b) => b.path.length - a.path.length)[0]
)
}
function mockHandle(handler) {
if (Object.prototype.toString.call(handler) === '[object Function]')
return handler
return (_, res) => {
return res.end(JSON.stringify(handler))
}
}
function parseMockUrl(key) {
let method = 'get'
let path = key
if (/\s+/.test(key)) {
const splited = key.split(/\s+/)
method = splited[0].toLowerCase()
path = splited[1]
}
return {
method,
path,
}
}
function initMock(config) {
try {
_mockconfig = config
mockList = []
const files = getMockFile()
const mockData = getMockData(files)
parseMockData(mockData)
}
catch (e) {
signale.warn('init mock failed')
signale.fatal(e)
}
}
module.exports = {
getMatchMock,
initMock,
}
+95
View File
@@ -0,0 +1,95 @@
const { join, isAbsolute } = require('path')
const { existsSync } = require('fs')
const { winPath } = require('./utils')
let files = null
function initFiles(cwd) {
if (files)
return
files = getConfigPaths(cwd)
}
function getConfigPaths(cwd) {
const env = process.env.UMI_ENV
return [
join(cwd, 'config/'),
join(cwd, '.umirc.js'),
join(cwd, '.umirc.ts'),
join(cwd, '.umirc.local.js'),
join(cwd, '.umirc.local.ts'),
...(env ? [join(cwd, `.umirc.${env}.js`), join(cwd, `.umirc.${env}.ts`)] : []),
]
}
function addBabelRegisterFiles(extraFiles, { cwd }) {
initFiles(cwd)
files = files.concat(...extraFiles)
}
function addBabelRegister({ cwd }) {
initFiles(cwd)
const only = files.map((f) => {
const fullPath = isAbsolute(f) ? f : join(cwd, f)
return winPath(fullPath)
})
let absSrcPath = join(cwd, 'src')
if (!existsSync(absSrcPath))
absSrcPath = cwd
registerBabel({
// only suport glob
// ref: https://babeljs.io/docs/en/next/babel-core.html#configitem-type
only,
babelPreset: [
require.resolve('@babel/preset-env'),
{
targets: { browsers: ['last 2 versions'] },
loose: false,
modules: 'commonjs',
exclude: [
'transform-typeof-symbol',
'transform-unicode-regex',
'transform-sticky-regex',
'transform-new-target',
'transform-modules-umd',
'transform-modules-systemjs',
'transform-modules-amd',
'transform-literals',
],
},
],
babelPlugins: [
[
require.resolve('babel-plugin-module-resolver'),
{
alias: {
'@': absSrcPath,
},
},
],
],
})
}
function configBabelRegister(files, { cwd }) {
addBabelRegisterFiles(files, {
cwd,
})
addBabelRegister({
cwd,
})
}
function registerBabel(opts = {}) {
const { only, ignore, babelPreset, babelPlugins } = opts
if (!process.env.IS_FROM_UMI_TEST) {
require('@babel/register')({
presets: [require.resolve('@babel/preset-typescript'), babelPreset],
plugins: babelPlugins || [],
only,
ignore,
extensions: ['.es6', '.es', '.jsx', '.js', '.mjs', '.ts', '.tsx'],
babelrc: false,
cache: false,
})
}
}
module.exports = { addBabelRegisterFiles, addBabelRegister, configBabelRegister }
+9
View File
@@ -0,0 +1,9 @@
const slash = require('slash2')
const winPath = function (path) {
return slash(path)
}
module.exports = {
winPath,
}