Compare commits

..

No commits in common. "master" and "1.1.11" have entirely different histories.

11 changed files with 130 additions and 358 deletions

21
LICENSE
View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2018
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -19,7 +19,6 @@ const IS_WINDOWS = process.platform === 'win32'
const CONFIG_FILE = normalize(join(WORK_SPACE, 'fite.config.js')) const CONFIG_FILE = normalize(join(WORK_SPACE, 'fite.config.js'))
const PROTOCOL = IS_WINDOWS ? 'file://' : '' const PROTOCOL = IS_WINDOWS ? 'file://' : ''
const NODE_VERSION = process.versions.node.split('.').map(n => +n) const NODE_VERSION = process.versions.node.split('.').map(n => +n)
const ABS_CONFIG_FILEPATH = PROTOCOL + CONFIG_FILE
let args = process.argv.slice(2) let args = process.argv.slice(2)
let mode = args.shift() || 'prod' let mode = args.shift() || 'prod'
@ -41,9 +40,8 @@ switch (mode) {
case 'dev': case 'dev':
process.env.NODE_ENV = 'development' process.env.NODE_ENV = 'development'
import(ABS_CONFIG_FILEPATH) import(PROTOCOL + CONFIG_FILE)
.then(function (conf) { .then(function (conf) {
conf.default.ABS_CONFIG_FILEPATH = ABS_CONFIG_FILEPATH
createServer(WORK_SPACE, conf.default) createServer(WORK_SPACE, conf.default)
}) })
.catch(err => { .catch(err => {
@ -54,14 +52,13 @@ switch (mode) {
case 'build': case 'build':
process.env.NODE_ENV = 'production' process.env.NODE_ENV = 'production'
import(ABS_CONFIG_FILEPATH) import(PROTOCOL + CONFIG_FILE)
.then(function (conf) { .then(function (conf) {
let dist = conf.buildDir || 'dist' let dist = conf.buildDir || 'dist'
if (clean && fs.isdir(dist)) { if (clean && fs.isdir(dist)) {
console.log('清除dist目录...') console.log('清除dist目录...')
fs.rm(dist) fs.rm(dist)
} }
conf.default.ABS_CONFIG_FILEPATH = ABS_CONFIG_FILEPATH
compile(WORK_SPACE, dist, conf.default, verbose) compile(WORK_SPACE, dist, conf.default, verbose)
}) })
.catch(err => { .catch(err => {

View File

@ -18,8 +18,7 @@ import {
CSS_SHEET_EXP, CSS_SHEET_EXP,
V_DEEP, V_DEEP,
PERCENT_EXP, PERCENT_EXP,
SHEETS_DEF, SHEETS_DEF
LEGACY_POLYFILL
} from './constants.js' } from './constants.js'
import { createHmrScript, md5, uuid, urlJoin } from './utils.js' import { createHmrScript, md5, uuid, urlJoin } from './utils.js'
@ -27,10 +26,6 @@ const OPTIONS = {
style: 'compressed' style: 'compressed'
} }
function minify(code) {
return Es.transformSync(code, { minify: true }).code.trim()
}
// 处理css中的 :deep() // 处理css中的 :deep()
function parseVDeep(curr, val, scoped) { function parseVDeep(curr, val, scoped) {
let res = V_DEEP.exec(curr) let res = V_DEEP.exec(curr)
@ -64,7 +59,7 @@ function scopeCss(css = '', hash) {
if (last.startsWith(':')) { if (last.startsWith(':')) {
output = parseVDeep(last, output, true) output = parseVDeep(last, output, true)
} else { } else {
output = `${last}[data-${hash}] ${output}` output = `${last} ${output}`
} }
} else { } else {
if (last.includes(':')) { if (last.includes(':')) {
@ -115,9 +110,8 @@ export function compileScss(file, inject = '') {
export function parseJs( export function parseJs(
code = '', code = '',
imports, imports,
{ IS_MPA, currentPage, IS_ENTRY, DEPLOY_PATH, LEGACY_MODE, define } = {}, { IS_MPA, currentPage, IS_ENTRY, DEPLOY_PATH, LEGACY_MODE } = {},
filename, filename
linePatch = 1
) { ) {
let fixedStyle = '' let fixedStyle = ''
let ASSETS_DIR = '/@/' let ASSETS_DIR = '/@/'
@ -131,21 +125,16 @@ export function parseJs(
code = Es.transformSync(code).code || '' code = Es.transformSync(code).code || ''
} catch (e) { } catch (e) {
let err = e.errors.pop() let err = e.errors.pop()
let lines = code.split('\n') console.log('%s: %s', red('Uncaught SyntaxError'), err.text)
console.log(
console.log('%s: %s', red('Uncaught SyntaxError'), red(err.text)) ' @ line %d: %s',
// 将上下文几行都打印出来 err.location.line,
for (let i = err.location.line - 3; i <= err.location.line + 1; i++) { cyan(err.location.lineText)
console.log( )
' @ line %d: %s',
i + linePatch,
err.location.line === i + 1 ? red(lines[i]) : lines[i]
)
}
console.log( console.log(
' @ %s:%d:%d', ' @ %s:%d:%d',
blue(filename), blue(filename),
err.location.line + linePatch - 1, err.location.line,
err.location.column err.location.column
) )
} }
@ -154,7 +143,7 @@ export function parseJs(
.replace(/\r\n/g, '\n') .replace(/\r\n/g, '\n')
.replace(/process\.env\.NODE_ENV/g, `'${process.env.NODE_ENV}'`) .replace(/process\.env\.NODE_ENV/g, `'${process.env.NODE_ENV}'`)
.replace( .replace(
/(import|export) ([^'"]*?) from (["'])(.*?)\3/g, /(import|export) ([\w\W]*?) from (["'])(.*?)\3/g,
function (m, t, alias, q, name) { function (m, t, alias, q, name) {
if (name.startsWith('@/')) { if (name.startsWith('@/')) {
name = name.replace('@/', urlJoin(DEPLOY_PATH, ASSETS_DIR)) name = name.replace('@/', urlJoin(DEPLOY_PATH, ASSETS_DIR))
@ -191,19 +180,9 @@ export function parseJs(
if (isBuild) { if (isBuild) {
name = name.replace(/\.vue$/, '.js') name = name.replace(/\.vue$/, '.js')
} }
if (alias.trim() === '*') { return `import ${alias} from '${name}'${
return `${t} ${alias} from '${name}'` t === 'export' ? `\nexport ${alias}` : ''
} }`
let _alias = alias
let _import = ''
if (alias.includes('* as')) {
_alias = ' default ' + alias.replace('* as', '').trim()
}
_import = `import ${alias} from '${name}'`
_import += t === 'export' ? `\nexport ${_alias}` : ''
return _import
} }
) )
.replace(/import\((['"])(.*?)\1\)/g, function (m, q, name) { .replace(/import\((['"])(.*?)\1\)/g, function (m, q, name) {
@ -213,9 +192,6 @@ export function parseJs(
if (name.startsWith('@/')) { if (name.startsWith('@/')) {
name = name.replace('@/', urlJoin(DEPLOY_PATH, ASSETS_DIR)) name = name.replace('@/', urlJoin(DEPLOY_PATH, ASSETS_DIR))
} }
if (name.endsWith('.json')) {
name += '.js'
}
return `import('${name}')` return `import('${name}')`
}) })
.replace(/import (["'])(.*?)\1/g, function (m, q, name) { .replace(/import (["'])(.*?)\1/g, function (m, q, name) {
@ -231,30 +207,19 @@ export function parseJs(
let _name = name.replace(/\\/g, '/').replace('@/', '') let _name = name.replace(/\\/g, '/').replace('@/', '')
let tmp = `style_${uuid()}` let tmp = `style_${uuid()}`
// 因为esm语法的变更, 原先的 import xx from xx assets {type: css} 变为了 with
// 而这个语法的变化, 构建工具无法做版本判断, 故, 统一降级到fetch()加载
if (LEGACY_MODE) { if (LEGACY_MODE) {
fixedStyle += fixedStyle += `${tmp}.then(r => {
`{\n` + let stylesheet = document.createElement('style')
` let stylesheet = document.createElement('style');\n` + stylesheet.setAttribute('name', '${_name}')
` stylesheet.setAttribute('name', '${_name}');\n` + stylesheet.textContent = r
` stylesheet.textContent = ${tmp};\n` + document.head.appendChild(stylesheet)
` document.head.appendChild(stylesheet);\n` + })
`}\n` `
return `const ${tmp} = window.fetch('${name}').then(r => r.text())`
return `let ${tmp};\n!(async function(){\n ${tmp} = await __fite_import('${name}', import.meta.url);\n})()`
} else { } else {
// CSSStyleSheet.replaceSync 需要FF v101, Safari 16.4才支持 fixedStyle += `${tmp}.path = '${_name}'\n__sheets__.push(${tmp})\n`
fixedStyle +=
`{\n` +
` let stylesheet = new CSSStyleSheet();\n` +
` stylesheet.path = '${_name}';\n` +
` stylesheet.replaceSync(${tmp} );\n` +
` __sheets__.push(stylesheet);\n` +
` document.adoptedStyleSheets = __sheets__;\n` +
`}\n`
return `const ${tmp} = await __fite_import('${name}', import.meta.url)` return `import ${tmp} from '${name}' assert { type: 'css' }`
} }
} else { } else {
if (name.startsWith('@/')) { if (name.startsWith('@/')) {
@ -290,10 +255,6 @@ export function parseJs(
} }
}) })
for (let key in define) {
code = code.replaceAll(key, define[key])
}
if (fixedStyle) { if (fixedStyle) {
code += '\n\n' + (IS_ENTRY ? SHEETS_DEF : '') + fixedStyle code += '\n\n' + (IS_ENTRY ? SHEETS_DEF : '') + fixedStyle
@ -310,7 +271,7 @@ export function parseJs(
* @param file <String> 文件路径 * @param file <String> 文件路径
* @return <String> 返回转换后的js代码 * @return <String> 返回转换后的js代码
*/ */
export async function compileVue(file, imports, options = {}) { export function compileVue(file, imports, options = {}) {
// 修正那反人类的windows路径 // 修正那反人类的windows路径
let filename = file.slice(options.SOURCE_DIR.length).replace(/\\/g, '/') let filename = file.slice(options.SOURCE_DIR.length).replace(/\\/g, '/')
let code = (fs.cat(file) || '').toString().replace(/\r\n/g, '\n') let code = (fs.cat(file) || '').toString().replace(/\r\n/g, '\n')
@ -322,7 +283,6 @@ export async function compileVue(file, imports, options = {}) {
let js = code.match(JS_EXP) let js = code.match(JS_EXP)
let scss = [...code.matchAll(STYLE_EXP)] let scss = [...code.matchAll(STYLE_EXP)]
let html = code.match(HTML_EXP) || ['', ''] let html = code.match(HTML_EXP) || ['', '']
let linePatch = code.slice(0, js?.index || 0).split('\n').length // js起始行数修正
let hash = md5(file) let hash = md5(file)
let scopeId = 'data-' + hash let scopeId = 'data-' + hash
@ -340,15 +300,10 @@ export async function compileVue(file, imports, options = {}) {
} else { } else {
css = compileScss(it[1], options.INJECT_SCSS) css = compileScss(it[1], options.INJECT_SCSS)
} }
return css return css
}) })
.join(' ') .join(' ')
for (let fn of options.plugin) {
scss = await fn('css', scss)
}
js = js ? js[1] : 'export default {}' js = js ? js[1] : 'export default {}'
try { try {
@ -362,8 +317,9 @@ export async function compileVue(file, imports, options = {}) {
isCustomElement isCustomElement
}).code.replace('export function render', 'function render') }).code.replace('export function render', 'function render')
} catch (err) { } catch (err) {
let lines = html[1].split('\n') // console.log(err)
let line = lines[err.loc?.start.line - 1] let tmp = html[1].split('\n')
let line = tmp[err.loc?.start.line - 1]
console.log('%s: %s', red('SyntaxError'), red(err.message)) console.log('%s: %s', red('SyntaxError'), red(err.message))
console.log( console.log(
@ -404,7 +360,7 @@ function render(_ctx, _cache) {
CACHE[file] = { changed: false, js, html } CACHE[file] = { changed: false, js, html }
} }
output += parseJs(js, imports, options, file, linePatch).replace( output += parseJs(js, imports, options, file).replace(
'export default {', 'export default {',
`\n${ `\n${
options.LEGACY_MODE ? '' : SHEETS_DEF options.LEGACY_MODE ? '' : SHEETS_DEF
@ -425,13 +381,13 @@ function render(_ctx, _cache) {
` `
} else { } else {
output += ` output += `
{ {
let stylesheet = new CSSStyleSheet() let stylesheet = new CSSStyleSheet()
stylesheet.path = '${filename}' stylesheet.path = '${filename}'
stylesheet.replaceSync(\`${scss}\`) stylesheet.replaceSync(\`${scss}\`)
__sheets__.push(stylesheet) __sheets__.push(stylesheet)
} }
document.adoptedStyleSheets = __sheets__ document.adoptedStyleSheets = __sheets__
` `
} }
} }
@ -446,15 +402,23 @@ document.adoptedStyleSheets = __sheets__
/** /**
* 解析模板html * 解析模板html
*/ */
export function parseHtml(html, { page, imports, entry, LEGACY_MODE }) { export function parseHtml(
html,
{ page, imports, entry, LEGACY_MODE, session }
) {
return html return html
.replace(/\r\n/g, '\n') .replace(/\r\n/g, '\n')
.replace( .replace(
'</head>', '</head>',
`${ `${
process.env.NODE_ENV === 'development' process.env.NODE_ENV === 'development'
? ` <script>${minify(createHmrScript(LEGACY_MODE))}</script>\n` ? ` <script>${Es.transformSync(
: ` <script>${minify(LEGACY_POLYFILL)}</script>\n` createHmrScript(LEGACY_MODE, session),
{
minify: true
}
).code.trim()}</script>\n`
: ''
}</head>` }</head>`
) )
.replace('{{title}}', page.title || '') .replace('{{title}}', page.title || '')

View File

@ -18,7 +18,6 @@ export async function compileFiles(
options, options,
{ verbose, imports, dist } = {} { verbose, imports, dist } = {}
) { ) {
let pageDir = options.IS_MPA && currentPage ? `pages/${currentPage}` : ''
options.currentPage = currentPage options.currentPage = currentPage
for (let [path, it] of files) { for (let [path, it] of files) {
@ -41,17 +40,16 @@ export async function compileFiles(
verbose && console.log(' 解析 %s ...', it.name) verbose && console.log(' 解析 %s ...', it.name)
let pageDir = options.IS_MPA && currentPage ? `pages/${currentPage}` : ''
switch (it.ext) { switch (it.ext) {
case '.vue': case '.vue':
{ {
let code = await compileVue(path, imports, options) let code = compileVue(path, imports, options)
await Es.transform(code, { minify: true }).then(async ({ code }) => { await Es.transform(code, { minify: true }).then(r => {
for (let fn of options.plugin) {
code = await fn('js', code)
}
fs.echo( fs.echo(
code, r.code,
join(dist, 'assets/', pageDir, it.name.replace(/\.vue$/, '.js')) join(dist, 'assets/', pageDir, it.name.replace(/\.vue$/, '.js'))
) )
}) })
@ -64,25 +62,11 @@ export async function compileFiles(
code = parseJs(code + '', imports, options) code = parseJs(code + '', imports, options)
await Es.transform(code, { minify: true }).then(async ({ code }) => { await Es.transform(code, { minify: true }).then(r => {
for (let fn of options.plugin) { fs.echo(r.code, join(dist, 'assets/', pageDir, it.name))
code = await fn('js', code)
}
fs.echo(code, join(dist, 'assets/', pageDir, it.name))
}) })
} }
break break
// es2024之后esm的语法的assets 变成了with, 对构建工具来说无法适配到具体的浏览器
// 故把json文件改成js文件
case '.json':
{
let code = fs.cat(path)
code = 'export default ' + JSON.stringify(JSON.parse(code + ''))
fs.echo(code, join(dist, 'assets/', pageDir, it.name + '.js'))
}
break
case '.scss': case '.scss':
case '.css': case '.css':
@ -97,9 +81,6 @@ export async function compileFiles(
fs.cp(path, target) fs.cp(path, target)
} else { } else {
let code = compileScss(path) let code = compileScss(path)
for (let fn of options.plugin) {
code = await fn('css', code)
}
fs.echo(code, target) fs.echo(code, target)
} }
} }

View File

@ -18,33 +18,3 @@ export const COMMON_HEADERS = {
export const SHEETS_DEF = export const SHEETS_DEF =
'const __sheets__ = [...document.adoptedStyleSheets];\n' 'const __sheets__ = [...document.adoptedStyleSheets];\n'
export const LEGACY_POLYFILL = `!(function(){
function join(p1, p2) {
let tmp1 = p1.split('/')
let tmp2 = p2.split('/')
if (tmp1.at(-1) === '') {
tmp1.pop()
}
while (tmp2.length) {
let tmp = tmp2.shift()
if (tmp === '.' || tmp === '') {
continue
} else if (tmp === '..') {
tmp1.pop()
} else {
tmp1.push(tmp)
}
}
return tmp1.join('/')
}
window.__fite_import = function(url,relPath){
let absPath = relPath.split('/').slice(0, -1).join('/')
let req
if(url.startsWith('./') || url.startsWith('../')) {
url = join(absPath, url)
}
return window.fetch(url).then(r => r.text())
}
})()`

View File

@ -7,7 +7,7 @@ import socket from './ws.js'
import chokidar from 'chokidar' import chokidar from 'chokidar'
import { red } from 'kolorist' import { red } from 'kolorist'
import { friendlyErrors, defaultCustomElement, gzip } from './utils.js' import { friendlyErrors, defaultCustomElement, md5, gzip } from './utils.js'
import { compileScss, parseJs, compileVue, parseHtml } from './compile-vue.js' import { compileScss, parseJs, compileVue, parseHtml } from './compile-vue.js'
@ -34,7 +34,6 @@ export default async function createServer(root = '', conf = {}) {
const LEGACY_MODE = !!conf.legacy const LEGACY_MODE = !!conf.legacy
const ENABLE_GZIP = !!conf.devServer.gzip const ENABLE_GZIP = !!conf.devServer.gzip
const { isCustomElement = defaultCustomElement } = conf.compileOptions || {} const { isCustomElement = defaultCustomElement } = conf.compileOptions || {}
const { plugin = [], define = {} } = conf
if (conf.imports['vue-dev']) { if (conf.imports['vue-dev']) {
conf.imports.vue = conf.imports['vue-dev'] conf.imports.vue = conf.imports['vue-dev']
@ -43,10 +42,6 @@ export default async function createServer(root = '', conf = {}) {
conf.imports['vue-router'] = conf.imports['vue-router-dev'] conf.imports['vue-router'] = conf.imports['vue-router-dev']
} }
if (conf.devServer.headers) {
Object.assign(COMMON_HEADERS, conf.devServer.headers)
}
if (USE_HTTPS) { if (USE_HTTPS) {
Object.assign(SERVER_OPTIONS, conf.devServer.ssl) Object.assign(SERVER_OPTIONS, conf.devServer.ssl)
@ -74,7 +69,7 @@ export default async function createServer(root = '', conf = {}) {
currentPage = '' currentPage = ''
server server
.on('request', async function (req, res) { .on('request', function (req, res) {
let prefix = DEPLOY_PATH ? DEPLOY_PATH.replace(/\/$/, '') : '' let prefix = DEPLOY_PATH ? DEPLOY_PATH.replace(/\/$/, '') : ''
let url = let url =
prefix && req.url.startsWith(prefix) prefix && req.url.startsWith(prefix)
@ -171,31 +166,20 @@ export default async function createServer(root = '', conf = {}) {
let entry = fs.cat(page.entry)?.toString() let entry = fs.cat(page.entry)?.toString()
let html = fs.cat(join(process.cwd(), 'index.html')).toString() let html = fs.cat(join(process.cwd(), 'index.html')).toString()
entry = parseJs( entry = parseJs(entry, conf.imports, {
entry, IS_MPA,
conf.imports, currentPage,
{ IS_ENTRY: true,
IS_MPA, DEPLOY_PATH,
currentPage, LEGACY_MODE
IS_ENTRY: true, })
DEPLOY_PATH,
LEGACY_MODE,
isCustomElement,
plugin,
define
},
page.entry
)
for (let fn of plugin) {
entry = await fn('js', entry)
}
code = parseHtml(html, { code = parseHtml(html, {
page, page,
imports: conf.imports, imports: conf.imports,
entry, entry,
LEGACY_MODE LEGACY_MODE,
session: md5(page.entry)
}) })
} }
@ -214,6 +198,7 @@ export default async function createServer(root = '', conf = {}) {
file = join(SOURCE_DIR, rpath) file = join(SOURCE_DIR, rpath)
} }
} else { } else {
ndex.html
file = join(SOURCE_DIR, rpath) file = join(SOURCE_DIR, rpath)
} }
if (!fs.isfile(file)) { if (!fs.isfile(file)) {
@ -223,7 +208,7 @@ export default async function createServer(root = '', conf = {}) {
return return
} }
code = await compileVue(file, conf.imports, { code = compileVue(file, conf.imports, {
IS_MPA, IS_MPA,
currentPage, currentPage,
SOURCE_DIR, SOURCE_DIR,
@ -231,15 +216,9 @@ export default async function createServer(root = '', conf = {}) {
DEPLOY_PATH, DEPLOY_PATH,
INJECT_SCSS, INJECT_SCSS,
LEGACY_MODE, LEGACY_MODE,
isCustomElement, isCustomElement
plugin,
define
}) })
for (let fn of plugin) {
code = await fn('js', code)
}
res.setHeader('content-type', MIME_TYPES.js) res.setHeader('content-type', MIME_TYPES.js)
} }
break break
@ -265,25 +244,14 @@ export default async function createServer(root = '', conf = {}) {
return return
} }
code = compileScss(file) code = compileScss(file)
for (let fn of plugin) {
code = await fn('css', code)
}
res.setHeader('content-type', MIME_TYPES.css) res.setHeader('content-type', MIME_TYPES.css)
} }
break break
case 'js': case 'js':
case 'wasm':
{ {
let rpath = pathname.replace('@/', '') let rpath = pathname.replace('@/', '')
let file let file
let isJson = false
let isWasm = rpath.endsWith('.wasm')
if (rpath.endsWith('json.js')) {
isJson = true
rpath = rpath.slice(0, -3)
}
if (IS_MPA) { if (IS_MPA) {
// 判断前后2个值相等, 避免出现目录名和页面名字相同时走错逻辑 // 判断前后2个值相等, 避免出现目录名和页面名字相同时走错逻辑
@ -299,43 +267,20 @@ export default async function createServer(root = '', conf = {}) {
if (fs.isfile(file)) { if (fs.isfile(file)) {
code = fs.cat(file) code = fs.cat(file)
} else if (fs.isfile(join(PUBLIC_DIR, rpath))) { } else if (fs.isfile(join(PUBLIC_DIR, rpath))) {
file = join(PUBLIC_DIR, rpath) code = fs.cat(join(PUBLIC_DIR, rpath))
code = fs.cat(file)
} else { } else {
friendlyErrors(rpath, ext) friendlyErrors(rpath, ext)
res.writeHead(404, USE_HTTPS ? void 0 : 'Not Found') res.writeHead(404, USE_HTTPS ? void 0 : 'Not Found')
res.end('') res.end('')
return return
} }
if (isJson) { code = parseJs(code + '', conf.imports, {
try { IS_MPA,
code = currentPage,
'export default ' + JSON.stringify(JSON.parse(code + '')) DEPLOY_PATH,
} catch (err) { LEGACY_MODE
console.log('%s 语法错误: %s', rpath, red(err.message)) })
} res.setHeader('content-type', MIME_TYPES.js)
} else if (isWasm) {
//
} else {
code = parseJs(
code + '',
conf.imports,
{
IS_MPA,
currentPage,
DEPLOY_PATH,
LEGACY_MODE,
isCustomElement,
plugin,
define
},
file
)
for (let fn of plugin) {
code = await fn('js', code)
}
}
res.setHeader('content-type', MIME_TYPES[ext])
} }
break break
@ -414,7 +359,7 @@ export default async function createServer(root = '', conf = {}) {
) )
chokidar chokidar
.watch([SOURCE_DIR, PUBLIC_DIR, join(root, './index.html')]) .watch([SOURCE_DIR, PUBLIC_DIR, join(root, './index.html')])
.on('all', async (act, filePath) => { .on('all', (act, filePath) => {
if (ready) { if (ready) {
let file = filePath.slice(SOURCE_DIR.length) let file = filePath.slice(SOURCE_DIR.length)
@ -434,9 +379,6 @@ export default async function createServer(root = '', conf = {}) {
} else { } else {
content = fs.cat(filePath).toString() content = fs.cat(filePath).toString()
} }
for (let fn of plugin) {
content = await fn('css', content)
}
ws.send({ ws.send({
action: 'render', action: 'render',
data: { path: file.replace(/\\/g, '/'), content } data: { path: file.replace(/\\/g, '/'), content }
@ -446,7 +388,7 @@ export default async function createServer(root = '', conf = {}) {
case 'vue': case 'vue':
{ {
let content = await compileVue(filePath, conf.imports, { let content = compileVue(filePath, conf.imports, {
IS_MPA, IS_MPA,
currentPage, currentPage,
SOURCE_DIR, SOURCE_DIR,
@ -454,9 +396,7 @@ export default async function createServer(root = '', conf = {}) {
DEPLOY_PATH, DEPLOY_PATH,
INJECT_SCSS, INJECT_SCSS,
LEGACY_MODE, LEGACY_MODE,
isCustomElement, isCustomElement
plugin,
define
}) })
let tmp = CACHE[filePath] let tmp = CACHE[filePath]
if (tmp.changed) { if (tmp.changed) {

View File

@ -3,14 +3,10 @@ import { Worker, parentPort } from 'node:worker_threads'
import os from 'node:os' import os from 'node:os'
import fs from 'iofs' import fs from 'iofs'
import { compileFiles } from './compile.js'
import { defaultCustomElement } from './utils.js'
const IS_WIN = process.platform === 'win32' const IS_WIN = process.platform === 'win32'
const PREFIX = IS_WIN ? 'pages\\' : 'pages/' const PREFIX = IS_WIN ? 'pages\\' : 'pages/'
// 4核(或4线程)以上的CPU, 才开启多线程编译。且线程开销太高, 开太多线程效率反而不高。 // 线程太多, 效率反而不高
const CPU_CORES = os.cpus().length > 5 ? 6 : os.cpus().length const THREADS_NUM = os.cpus().length > 4 ? 4 : os.cpus().length - 1
const THREADS_NUM = CPU_CORES > 3 ? CPU_CORES - 1 : 0
const __filename = normalize(import.meta.url.slice(IS_WIN ? 8 : 7)) const __filename = normalize(import.meta.url.slice(IS_WIN ? 8 : 7))
const __dirname = dirname(__filename) const __dirname = dirname(__filename)
const WORKER_POOL = new Set() // 线程池 const WORKER_POOL = new Set() // 线程池
@ -52,13 +48,7 @@ export default function compile(root = '', dist = '', conf = {}, verbose) {
) )
const INJECT_SCSS = readFile(conf.inject?.scss) const INJECT_SCSS = readFile(conf.inject?.scss)
const LEGACY_MODE = !!conf.legacy const LEGACY_MODE = !!conf.legacy
const { const { isCustomElement } = conf.compileOptions || {}
ABS_CONFIG_FILEPATH,
compileOptions = {},
define = {},
plugin = []
} = conf
const { isCustomElement = defaultCustomElement } = compileOptions
conf.inject = conf.inject || { scss: '' } conf.inject = conf.inject || { scss: '' }
@ -71,8 +61,22 @@ export default function compile(root = '', dist = '', conf = {}, verbose) {
DEPLOY_PATH, DEPLOY_PATH,
INJECT_SCSS, INJECT_SCSS,
LEGACY_MODE, LEGACY_MODE,
ABS_CONFIG_FILEPATH, // 线程通讯无法传递函数类型, 需要转为字符串, 之后再转回来
define isCustomElement: isCustomElement ? isCustomElement.toString() : null
}
// 创建线程池
for (let i = 0; i < THREADS_NUM; i++) {
WORKER_POOL.add(
new Worker(join(__dirname, './thread.js'), {
workerData: {
options,
verbose,
dist,
imports: conf.imports
}
})
)
} }
fs.ls(SOURCE_DIR, true).forEach(path => { fs.ls(SOURCE_DIR, true).forEach(path => {
@ -93,34 +97,14 @@ export default function compile(root = '', dist = '', conf = {}, verbose) {
return return
} }
if (path === conf.inject.scss) { if (it.path === conf.inject.scss) {
return return
} }
list.set(path, it) list.set(path, it)
} }
}) })
// 创建线程池
if (THREADS_NUM > 0 && (IS_MPA || list.size > THREADS_NUM * 10)) {
// 页面数过少时, 线程数量不能比页面数多
let max = Math.min(THREADS_NUM, PAGES_KEYS.length)
for (let i = 0; i < max; i++) {
WORKER_POOL.add(
new Worker(join(__dirname, './thread.js'), {
workerData: {
options,
verbose,
dist,
imports: conf.imports
}
})
)
}
} else {
options.isCustomElement = isCustomElement
}
// 优先处理静态目录, 之后的源码目录中, 以便如果有产生相同的文件名, 则覆盖静态目录中的文件 // 优先处理静态目录, 之后的源码目录中, 以便如果有产生相同的文件名, 则覆盖静态目录中的文件
if (fs.isdir(PUBLIC_DIR)) { if (fs.isdir(PUBLIC_DIR)) {
console.log('\n正在处理静态资源 ...') console.log('\n正在处理静态资源 ...')
@ -157,35 +141,18 @@ export default function compile(root = '', dist = '', conf = {}, verbose) {
list.delete(path) list.delete(path)
files.set(path, { name, ext }) files.set(path, { name, ext })
}) })
chunk.set(currentPage, { page, files })
if (THREADS_NUM > 0) { JOBS_QUEUE.push(chunk)
chunk.set(currentPage, { page, files }) doJob()
JOBS_QUEUE.push(chunk)
doJob()
} else {
console.log(`正在生成 ${currentPage}.html ...`)
compileFiles(currentPage, page, files, options, {
verbose,
dist,
imports: conf.imports
})
}
} }
// 公共依赖 // 公共依赖
if (THREADS_NUM > 0) { {
let chunk = new Map() let chunk = new Map()
chunk.set('', { page: null, files: list }) chunk.set('', { page: null, files: list })
JOBS_QUEUE.push(chunk) JOBS_QUEUE.push(chunk)
doJob() doJob()
} else {
console.log('\n正在解析公共依赖 ...')
compileFiles('', null, list, options, {
verbose,
dist,
imports: conf.imports
})
} }
} else { } else {
// 每个线程处理的文件数 // 每个线程处理的文件数
@ -193,28 +160,19 @@ export default function compile(root = '', dist = '', conf = {}, verbose) {
let currentPage = PAGES_KEYS[0] let currentPage = PAGES_KEYS[0]
let page = conf.pages[currentPage] let page = conf.pages[currentPage]
list = [...list]
console.log(`正在生成 ${currentPage}.html ...`) console.log(`正在生成 ${currentPage}.html ...`)
if (THREADS_NUM > 0 && list.size > THREADS_NUM * 10) { for (let i = 0; i < THREADS_NUM; i++) {
list = [...list] let start = i * chunkSize
for (let i = 0; i < THREADS_NUM; i++) { let end = start + chunkSize
let start = i * chunkSize let chunk = new Map()
let end = start + chunkSize
let chunk = new Map()
chunk.set(currentPage, { page, files: list.slice(start, end) }) chunk.set(currentPage, { page, files: list.slice(start, end) })
JOBS_QUEUE.push(chunk) JOBS_QUEUE.push(chunk)
doJob() doJob()
}
} else {
options.plugin = plugin
options.isCustomElement = isCustomElement
compileFiles(currentPage, page, list, options, {
verbose,
dist,
imports: conf.imports
})
} }
} }

View File

@ -8,17 +8,11 @@ import { compileFiles } from './compile.js'
import { defaultCustomElement } from './utils.js' import { defaultCustomElement } from './utils.js'
const { options, verbose, dist, imports } = workerData const { options, verbose, dist, imports } = workerData
const { ABS_CONFIG_FILEPATH } = options
const { compileOptions = {}, plugin = [] } = await import( options.isCustomElement = options.isCustomElement
ABS_CONFIG_FILEPATH ? Function('return ' + options.isCustomElement)()
).then(r => r.default) : defaultCustomElement
const { isCustomElement = defaultCustomElement } = compileOptions
options.isCustomElement = isCustomElement
options.plugin = plugin
//
async function doJob(job) { async function doJob(job) {
let [currentPage, { page, files }] = job.entries().next().value let [currentPage, { page, files }] = job.entries().next().value
@ -34,7 +28,7 @@ async function doJob(job) {
dist, dist,
imports imports
}) })
parentPort.postMessage(true) parentPort.postMessage('ok')
} }
parentPort.on('message', doJob) parentPort.on('message', doJob)

View File

@ -8,7 +8,6 @@ import { createHash, randomUUID } from 'node:crypto'
import { join } from 'node:path' import { join } from 'node:path'
import { gzipSync } from 'node:zlib' import { gzipSync } from 'node:zlib'
import { red, cyan, blue } from 'kolorist' import { red, cyan, blue } from 'kolorist'
import { LEGACY_POLYFILL } from './constants.js'
// 修正路径合并 避免在windows下被转义 // 修正路径合并 避免在windows下被转义
export function urlJoin(...args) { export function urlJoin(...args) {
@ -40,7 +39,7 @@ export function friendlyErrors(pathname, ext = '') {
export function createHmrScript(legacy, session = '') { export function createHmrScript(legacy, session = '') {
return ` return `
!(function vue_live_hmr(){ !(function vue_live_hmr(){
let ws = new WebSocket(\`ws\${location.protocol === 'https:' ? 's' : ''}://\${location.host}/ws-fite-hmr?session=\${btoa(location.pathname).replace(/[=\+\/]/g, '')}&lock=\${localStorage.getItem(location.pathname) || 0}\`) var ws = new WebSocket(\`ws\${location.protocol === 'https:' ? 's' : ''}://\${location.host}/ws-fite-hmr?session=${session}\`)
ws.addEventListener('open', function (r) { ws.addEventListener('open', function (r) {
if(vue_live_hmr.closed){ if(vue_live_hmr.closed){
@ -52,9 +51,6 @@ export function createHmrScript(legacy, session = '') {
ws.addEventListener('close', function(){ ws.addEventListener('close', function(){
vue_live_hmr.closed = true vue_live_hmr.closed = true
if (localStorage.getItem(location.pathname) === '1') {
return
}
setTimeout(vue_live_hmr, 2000) setTimeout(vue_live_hmr, 2000)
}) })
@ -94,7 +90,6 @@ export function createHmrScript(legacy, session = '') {
break break
} }
}) })
${LEGACY_POLYFILL}
})() })()
` `
} }

View File

@ -15,21 +15,16 @@ class WebSocket {
conn.on('connection', (client, req) => { conn.on('connection', (client, req) => {
let params = new URLSearchParams(req.url.slice(req.url.indexOf('?'))) let params = new URLSearchParams(req.url.slice(req.url.indexOf('?')))
let session = params.get('session') let session = params.get('session')
let lock = +params.get('lock')
if (lock === 1) { this.#clients.set(session, client)
client.close()
} else {
this.#clients.set(session, client)
client.once('close', _ => { client.once('close', _ => {
this.#clients.delete(session) this.#clients.delete(session)
}) })
while (this.#queue.length) { while (this.#queue.length) {
let msg = this.#queue.shift() let msg = this.#queue.shift()
this.send(msg) this.send(msg)
}
} }
}) })
} }

View File

@ -1,7 +1,7 @@
{ {
"name": "fite", "name": "fite",
"type": "module", "type": "module",
"version": "1.4.4", "version": "1.1.11",
"bin": { "bin": {
"fite": "index.js" "fite": "index.js"
}, },
@ -19,6 +19,5 @@
}, },
"engines": { "engines": {
"node": ">=16.6.0" "node": ">=16.6.0"
}, }
"license": "MIT"
} }