Compare commits
	
		
			No commits in common. "master" and "threads" have entirely different histories. 
		
	
	
		
							
								
								
									
										21
									
								
								LICENSE
								
								
								
								
							
							
						
						
									
										21
									
								
								LICENSE
								
								
								
								
							|  | @ -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. | ||||
|  | @ -23,9 +23,9 @@ | |||
| ### 你需要知道的几个事情 | ||||
| 
 | ||||
| - 因为没有打包, 所以所有的文件引用都是按源代码的结构, 对于源码的保护比较弱(虽然打包也没约等于没保护, 因为前端没秘密)。 | ||||
| - 因为是用的是原生的`ESM`,所以引用的**依赖/文件**, 需要完整的路径, 可以省略后缀名(不推荐), 但不能省略`index.js/index.vue`。 | ||||
| - 因为是用的是原生的`ESM`,所以引用的**依赖/文件**, 需要完整的路径, 可以省略后缀名, 但不能省略`index.js/index.vue`。 | ||||
| - 因为没有内置完整的样式处理,支持`scoped`、`:deep()`, 但不支持`:global()`。 | ||||
| - `单文件组件`中的样式, 如果是用scss, 不支持引用其他文件。 | ||||
| - `单文件组件`中的样式, 如果是用scss, 不支持引用其他文件, 也不支持设置共用定义文件。 | ||||
| - 样式预处理器, 只支持scss, 不支持less。 | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										7
									
								
								index.js
								
								
								
								
							
							
						
						
									
										7
									
								
								index.js
								
								
								
								
							|  | @ -19,7 +19,6 @@ const IS_WINDOWS = process.platform === 'win32' | |||
| const CONFIG_FILE = normalize(join(WORK_SPACE, 'fite.config.js')) | ||||
| const PROTOCOL = IS_WINDOWS ? 'file://' : '' | ||||
| const NODE_VERSION = process.versions.node.split('.').map(n => +n) | ||||
| const ABS_CONFIG_FILEPATH = PROTOCOL + CONFIG_FILE | ||||
| 
 | ||||
| let args = process.argv.slice(2) | ||||
| let mode = args.shift() || 'prod' | ||||
|  | @ -41,9 +40,8 @@ switch (mode) { | |||
|   case 'dev': | ||||
|     process.env.NODE_ENV = 'development' | ||||
| 
 | ||||
|     import(ABS_CONFIG_FILEPATH) | ||||
|     import(PROTOCOL + CONFIG_FILE) | ||||
|       .then(function (conf) { | ||||
|         conf.default.ABS_CONFIG_FILEPATH = ABS_CONFIG_FILEPATH | ||||
|         createServer(WORK_SPACE, conf.default) | ||||
|       }) | ||||
|       .catch(err => { | ||||
|  | @ -54,14 +52,13 @@ switch (mode) { | |||
|   case 'build': | ||||
|     process.env.NODE_ENV = 'production' | ||||
| 
 | ||||
|     import(ABS_CONFIG_FILEPATH) | ||||
|     import(PROTOCOL + CONFIG_FILE) | ||||
|       .then(function (conf) { | ||||
|         let dist = conf.buildDir || 'dist' | ||||
|         if (clean && fs.isdir(dist)) { | ||||
|           console.log('清除dist目录...') | ||||
|           fs.rm(dist) | ||||
|         } | ||||
|         conf.default.ABS_CONFIG_FILEPATH = ABS_CONFIG_FILEPATH | ||||
|         compile(WORK_SPACE, dist, conf.default, verbose) | ||||
|       }) | ||||
|       .catch(err => { | ||||
|  |  | |||
|  | @ -18,8 +18,7 @@ import { | |||
|   CSS_SHEET_EXP, | ||||
|   V_DEEP, | ||||
|   PERCENT_EXP, | ||||
|   SHEETS_DEF, | ||||
|   LEGACY_POLYFILL | ||||
|   SHEETS_DEF | ||||
| } from './constants.js' | ||||
| import { createHmrScript, md5, uuid, urlJoin } from './utils.js' | ||||
| 
 | ||||
|  | @ -27,10 +26,6 @@ const OPTIONS = { | |||
|   style: 'compressed' | ||||
| } | ||||
| 
 | ||||
| function minify(code) { | ||||
|   return Es.transformSync(code, { minify: true }).code.trim() | ||||
| } | ||||
| 
 | ||||
| // 处理css中的 :deep()
 | ||||
| function parseVDeep(curr, val, scoped) { | ||||
|   let res = V_DEEP.exec(curr) | ||||
|  | @ -64,7 +59,7 @@ function scopeCss(css = '', hash) { | |||
|               if (last.startsWith(':')) { | ||||
|                 output = parseVDeep(last, output, true) | ||||
|               } else { | ||||
|                 output = `${last}[data-${hash}] ${output}` | ||||
|                 output = `${last} ${output}` | ||||
|               } | ||||
|             } else { | ||||
|               if (last.includes(':')) { | ||||
|  | @ -115,9 +110,8 @@ export function compileScss(file, inject = '') { | |||
| export function parseJs( | ||||
|   code = '', | ||||
|   imports, | ||||
|   { IS_MPA, currentPage, IS_ENTRY, DEPLOY_PATH, LEGACY_MODE, define } = {}, | ||||
|   filename, | ||||
|   linePatch = 1 | ||||
|   { IS_MPA, currentPage, IS_ENTRY, DEPLOY_PATH, LEGACY_MODE } = {}, | ||||
|   filename | ||||
| ) { | ||||
|   let fixedStyle = '' | ||||
|   let ASSETS_DIR = '/@/' | ||||
|  | @ -131,21 +125,16 @@ export function parseJs( | |||
|     code = Es.transformSync(code).code || '' | ||||
|   } catch (e) { | ||||
|     let err = e.errors.pop() | ||||
|     let lines = code.split('\n') | ||||
| 
 | ||||
|     console.log('%s: %s', red('Uncaught SyntaxError'), red(err.text)) | ||||
|     // 将上下文几行都打印出来
 | ||||
|     for (let i = err.location.line - 3; i <= err.location.line + 1; i++) { | ||||
|       console.log( | ||||
|         '  @ line %d: %s', | ||||
|         i + linePatch, | ||||
|         err.location.line === i + 1 ? red(lines[i]) : lines[i] | ||||
|       ) | ||||
|     } | ||||
|     console.log('%s: %s', red('Uncaught SyntaxError'), err.text) | ||||
|     console.log( | ||||
|       '  @ line %d: %s', | ||||
|       err.location.line, | ||||
|       cyan(err.location.lineText) | ||||
|     ) | ||||
|     console.log( | ||||
|       '  @ %s:%d:%d', | ||||
|       blue(filename), | ||||
|       err.location.line + linePatch - 1, | ||||
|       err.location.line, | ||||
|       err.location.column | ||||
|     ) | ||||
|   } | ||||
|  | @ -154,7 +143,7 @@ export function parseJs( | |||
|     .replace(/\r\n/g, '\n') | ||||
|     .replace(/process\.env\.NODE_ENV/g, `'${process.env.NODE_ENV}'`) | ||||
|     .replace( | ||||
|       /(import|export) ([^'"]*?) from (["'])(.*?)\3/g, | ||||
|       /(import|export) ([\w\W]*?) from (["'])(.*?)\3/g, | ||||
|       function (m, t, alias, q, name) { | ||||
|         if (name.startsWith('@/')) { | ||||
|           name = name.replace('@/', urlJoin(DEPLOY_PATH, ASSETS_DIR)) | ||||
|  | @ -191,19 +180,9 @@ export function parseJs( | |||
|         if (isBuild) { | ||||
|           name = name.replace(/\.vue$/, '.js') | ||||
|         } | ||||
|         if (alias.trim() === '*') { | ||||
|           return `${t} ${alias} from '${name}'` | ||||
|         } | ||||
|         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 | ||||
|         return `import ${alias} from '${name}'${ | ||||
|           t === 'export' ? `\nexport ${alias}` : '' | ||||
|         }` | ||||
|       } | ||||
|     ) | ||||
|     .replace(/import\((['"])(.*?)\1\)/g, function (m, q, name) { | ||||
|  | @ -213,9 +192,6 @@ export function parseJs( | |||
|       if (name.startsWith('@/')) { | ||||
|         name = name.replace('@/', urlJoin(DEPLOY_PATH, ASSETS_DIR)) | ||||
|       } | ||||
|       if (name.endsWith('.json')) { | ||||
|         name += '.js' | ||||
|       } | ||||
|       return `import('${name}')` | ||||
|     }) | ||||
|     .replace(/import (["'])(.*?)\1/g, function (m, q, name) { | ||||
|  | @ -231,30 +207,19 @@ export function parseJs( | |||
|         let _name = name.replace(/\\/g, '/').replace('@/', '') | ||||
|         let tmp = `style_${uuid()}` | ||||
| 
 | ||||
|         // 因为esm语法的变更, 原先的 import xx from xx assets {type: css} 变为了 with
 | ||||
|         // 而这个语法的变化, 构建工具无法做版本判断, 故, 统一降级到fetch()加载
 | ||||
|         if (LEGACY_MODE) { | ||||
|           fixedStyle += | ||||
|             `{\n` + | ||||
|             `  let stylesheet = document.createElement('style');\n` + | ||||
|             `  stylesheet.setAttribute('name', '${_name}');\n` + | ||||
|             `  stylesheet.textContent = ${tmp};\n` + | ||||
|             `  document.head.appendChild(stylesheet);\n` + | ||||
|             `}\n` | ||||
| 
 | ||||
|           return `let ${tmp};\n!(async function(){\n  ${tmp} = await __fite_import('${name}', import.meta.url);\n})()` | ||||
|           fixedStyle += `${tmp}.then(r => {
 | ||||
|   let stylesheet = document.createElement('style') | ||||
|   stylesheet.setAttribute('name', '${_name}') | ||||
|   stylesheet.textContent = r | ||||
|   document.head.appendChild(stylesheet) | ||||
| }) | ||||
| ` | ||||
|           return `const ${tmp} = window.fetch('${name}').then(r => r.text())` | ||||
|         } else { | ||||
|           // CSSStyleSheet.replaceSync 需要FF v101, Safari 16.4才支持
 | ||||
|           fixedStyle += | ||||
|             `{\n` + | ||||
|             `  let stylesheet = new CSSStyleSheet();\n` + | ||||
|             `  stylesheet.path = '${_name}';\n` + | ||||
|             `  stylesheet.replaceSync(${tmp} );\n` + | ||||
|             `  __sheets__.push(stylesheet);\n` + | ||||
|             `  document.adoptedStyleSheets = __sheets__;\n` + | ||||
|             `}\n` | ||||
|           fixedStyle += `${tmp}.path = '${_name}'\n__sheets__.push(${tmp})\n` | ||||
| 
 | ||||
|           return `const ${tmp} = await __fite_import('${name}', import.meta.url)` | ||||
|           return `import ${tmp} from '${name}' assert { type: 'css' }` | ||||
|         } | ||||
|       } else { | ||||
|         if (name.startsWith('@/')) { | ||||
|  | @ -290,10 +255,6 @@ export function parseJs( | |||
|       } | ||||
|     }) | ||||
| 
 | ||||
|   for (let key in define) { | ||||
|     code = code.replaceAll(key, define[key]) | ||||
|   } | ||||
| 
 | ||||
|   if (fixedStyle) { | ||||
|     code += '\n\n' + (IS_ENTRY ? SHEETS_DEF : '') + fixedStyle | ||||
| 
 | ||||
|  | @ -310,7 +271,7 @@ export function parseJs( | |||
|  * @param file <String> 文件路径 | ||||
|  * @return <String> 返回转换后的js代码 | ||||
|  */ | ||||
| export async function compileVue(file, imports, options = {}) { | ||||
| export function compileVue(file, imports, options = {}) { | ||||
|   // 修正那反人类的windows路径
 | ||||
|   let filename = file.slice(options.SOURCE_DIR.length).replace(/\\/g, '/') | ||||
|   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 scss = [...code.matchAll(STYLE_EXP)] | ||||
|   let html = code.match(HTML_EXP) || ['', ''] | ||||
|   let linePatch = code.slice(0, js?.index || 0).split('\n').length // js起始行数修正
 | ||||
| 
 | ||||
|   let hash = md5(file) | ||||
|   let scopeId = 'data-' + hash | ||||
|  | @ -340,15 +300,10 @@ export async function compileVue(file, imports, options = {}) { | |||
|       } else { | ||||
|         css = compileScss(it[1], options.INJECT_SCSS) | ||||
|       } | ||||
| 
 | ||||
|       return css | ||||
|     }) | ||||
|     .join(' ') | ||||
| 
 | ||||
|   for (let fn of options.plugin) { | ||||
|     scss = await fn('css', scss) | ||||
|   } | ||||
| 
 | ||||
|   js = js ? js[1] : 'export default {}' | ||||
| 
 | ||||
|   try { | ||||
|  | @ -362,8 +317,9 @@ export async function compileVue(file, imports, options = {}) { | |||
|       isCustomElement | ||||
|     }).code.replace('export function render', 'function render') | ||||
|   } catch (err) { | ||||
|     let lines = html[1].split('\n') | ||||
|     let line = lines[err.loc?.start.line - 1] | ||||
|     // console.log(err)
 | ||||
|     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( | ||||
|  | @ -404,7 +360,7 @@ function render(_ctx, _cache) { | |||
|     CACHE[file] = { changed: false, js, html } | ||||
|   } | ||||
| 
 | ||||
|   output += parseJs(js, imports, options, file, linePatch).replace( | ||||
|   output += parseJs(js, imports, options, file).replace( | ||||
|     'export default {', | ||||
|     `\n${ | ||||
|       options.LEGACY_MODE ? '' : SHEETS_DEF | ||||
|  | @ -425,13 +381,13 @@ function render(_ctx, _cache) { | |||
| ` | ||||
|     } else { | ||||
|       output += ` | ||||
| { | ||||
|   let stylesheet = new CSSStyleSheet() | ||||
|   stylesheet.path = '${filename}' | ||||
|   stylesheet.replaceSync(\`${scss}\`)
 | ||||
|   __sheets__.push(stylesheet) | ||||
| } | ||||
| document.adoptedStyleSheets = __sheets__ | ||||
|   { | ||||
|     let stylesheet = new CSSStyleSheet() | ||||
|     stylesheet.path = '${filename}' | ||||
|     stylesheet.replaceSync(\`${scss}\`)
 | ||||
|     __sheets__.push(stylesheet) | ||||
|   } | ||||
|   document.adoptedStyleSheets = __sheets__ | ||||
| ` | ||||
|     } | ||||
|   } | ||||
|  | @ -446,15 +402,23 @@ document.adoptedStyleSheets = __sheets__ | |||
| /** | ||||
|  * 解析模板html | ||||
|  */ | ||||
| export function parseHtml(html, { page, imports, entry, LEGACY_MODE }) { | ||||
| export function parseHtml( | ||||
|   html, | ||||
|   { page, imports, entry, LEGACY_MODE, session } | ||||
| ) { | ||||
|   return html | ||||
|     .replace(/\r\n/g, '\n') | ||||
|     .replace( | ||||
|       '</head>', | ||||
|       `${ | ||||
|         process.env.NODE_ENV === 'development' | ||||
|           ? `  <script>${minify(createHmrScript(LEGACY_MODE))}</script>\n` | ||||
|           : `  <script>${minify(LEGACY_POLYFILL)}</script>\n` | ||||
|           ? `  <script>${Es.transformSync( | ||||
|               createHmrScript(LEGACY_MODE, session), | ||||
|               { | ||||
|                 minify: true | ||||
|               } | ||||
|             ).code.trim()}</script>\n` | ||||
|           : '' | ||||
|       }</head>` | ||||
|     ) | ||||
|     .replace('{{title}}', page.title || '') | ||||
|  |  | |||
|  | @ -18,7 +18,6 @@ export async function compileFiles( | |||
|   options, | ||||
|   { verbose, imports, dist } = {} | ||||
| ) { | ||||
|   let pageDir = options.IS_MPA && currentPage ? `pages/${currentPage}` : '' | ||||
|   options.currentPage = currentPage | ||||
| 
 | ||||
|   for (let [path, it] of files) { | ||||
|  | @ -41,17 +40,16 @@ export async function compileFiles( | |||
| 
 | ||||
|     verbose && console.log('  解析 %s ...', it.name) | ||||
| 
 | ||||
|     let pageDir = options.IS_MPA && currentPage ? `pages/${currentPage}` : '' | ||||
| 
 | ||||
|     switch (it.ext) { | ||||
|       case '.vue': | ||||
|         { | ||||
|           let code = await compileVue(path, imports, options) | ||||
|           let code = compileVue(path, imports, options) | ||||
| 
 | ||||
|           await Es.transform(code, { minify: true }).then(async ({ code }) => { | ||||
|             for (let fn of options.plugin) { | ||||
|               code = await fn('js', code) | ||||
|             } | ||||
|           await Es.transform(code, { minify: true }).then(r => { | ||||
|             fs.echo( | ||||
|               code, | ||||
|               r.code, | ||||
|               join(dist, 'assets/', pageDir, it.name.replace(/\.vue$/, '.js')) | ||||
|             ) | ||||
|           }) | ||||
|  | @ -64,25 +62,11 @@ export async function compileFiles( | |||
| 
 | ||||
|           code = parseJs(code + '', imports, options) | ||||
| 
 | ||||
|           await Es.transform(code, { minify: true }).then(async ({ code }) => { | ||||
|             for (let fn of options.plugin) { | ||||
|               code = await fn('js', code) | ||||
|             } | ||||
|             fs.echo(code, join(dist, 'assets/', pageDir, it.name)) | ||||
|           await Es.transform(code, { minify: true }).then(r => { | ||||
|             fs.echo(r.code, join(dist, 'assets/', pageDir, it.name)) | ||||
|           }) | ||||
|         } | ||||
|         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 '.css': | ||||
|  | @ -97,9 +81,6 @@ export async function compileFiles( | |||
|             fs.cp(path, target) | ||||
|           } else { | ||||
|             let code = compileScss(path) | ||||
|             for (let fn of options.plugin) { | ||||
|               code = await fn('css', code) | ||||
|             } | ||||
|             fs.echo(code, target) | ||||
|           } | ||||
|         } | ||||
|  |  | |||
|  | @ -18,33 +18,3 @@ export const COMMON_HEADERS = { | |||
| 
 | ||||
| export const SHEETS_DEF = | ||||
|   '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()) | ||||
|   } | ||||
| })()` | ||||
|  |  | |||
							
								
								
									
										110
									
								
								lib/dev.js
								
								
								
								
							
							
						
						
									
										110
									
								
								lib/dev.js
								
								
								
								
							|  | @ -7,7 +7,7 @@ import socket from './ws.js' | |||
| import chokidar from 'chokidar' | ||||
| 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' | ||||
| 
 | ||||
|  | @ -34,7 +34,6 @@ export default async function createServer(root = '', conf = {}) { | |||
|   const LEGACY_MODE = !!conf.legacy | ||||
|   const ENABLE_GZIP = !!conf.devServer.gzip | ||||
|   const { isCustomElement = defaultCustomElement } = conf.compileOptions || {} | ||||
|   const { plugin = [], define = {} } = conf | ||||
| 
 | ||||
|   if (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'] | ||||
|   } | ||||
| 
 | ||||
|   if (conf.devServer.headers) { | ||||
|     Object.assign(COMMON_HEADERS, conf.devServer.headers) | ||||
|   } | ||||
| 
 | ||||
|   if (USE_HTTPS) { | ||||
|     Object.assign(SERVER_OPTIONS, conf.devServer.ssl) | ||||
| 
 | ||||
|  | @ -74,7 +69,7 @@ export default async function createServer(root = '', conf = {}) { | |||
|     currentPage = '' | ||||
| 
 | ||||
|   server | ||||
|     .on('request', async function (req, res) { | ||||
|     .on('request', function (req, res) { | ||||
|       let prefix = DEPLOY_PATH ? DEPLOY_PATH.replace(/\/$/, '') : '' | ||||
|       let url = | ||||
|         prefix && req.url.startsWith(prefix) | ||||
|  | @ -171,31 +166,20 @@ export default async function createServer(root = '', conf = {}) { | |||
|               let entry = fs.cat(page.entry)?.toString() | ||||
|               let html = fs.cat(join(process.cwd(), 'index.html')).toString() | ||||
| 
 | ||||
|               entry = parseJs( | ||||
|                 entry, | ||||
|                 conf.imports, | ||||
|                 { | ||||
|                   IS_MPA, | ||||
|                   currentPage, | ||||
|                   IS_ENTRY: true, | ||||
|                   DEPLOY_PATH, | ||||
|                   LEGACY_MODE, | ||||
|                   isCustomElement, | ||||
|                   plugin, | ||||
|                   define | ||||
|                 }, | ||||
|                 page.entry | ||||
|               ) | ||||
| 
 | ||||
|               for (let fn of plugin) { | ||||
|                 entry = await fn('js', entry) | ||||
|               } | ||||
|               entry = parseJs(entry, conf.imports, { | ||||
|                 IS_MPA, | ||||
|                 currentPage, | ||||
|                 IS_ENTRY: true, | ||||
|                 DEPLOY_PATH, | ||||
|                 LEGACY_MODE | ||||
|               }) | ||||
| 
 | ||||
|               code = parseHtml(html, { | ||||
|                 page, | ||||
|                 imports: conf.imports, | ||||
|                 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) | ||||
|                 } | ||||
|               } else { | ||||
|                 ndex.html | ||||
|                 file = join(SOURCE_DIR, rpath) | ||||
|               } | ||||
|               if (!fs.isfile(file)) { | ||||
|  | @ -223,7 +208,7 @@ export default async function createServer(root = '', conf = {}) { | |||
|                 return | ||||
|               } | ||||
| 
 | ||||
|               code = await compileVue(file, conf.imports, { | ||||
|               code = compileVue(file, conf.imports, { | ||||
|                 IS_MPA, | ||||
|                 currentPage, | ||||
|                 SOURCE_DIR, | ||||
|  | @ -231,15 +216,9 @@ export default async function createServer(root = '', conf = {}) { | |||
|                 DEPLOY_PATH, | ||||
|                 INJECT_SCSS, | ||||
|                 LEGACY_MODE, | ||||
|                 isCustomElement, | ||||
|                 plugin, | ||||
|                 define | ||||
|                 isCustomElement | ||||
|               }) | ||||
| 
 | ||||
|               for (let fn of plugin) { | ||||
|                 code = await fn('js', code) | ||||
|               } | ||||
| 
 | ||||
|               res.setHeader('content-type', MIME_TYPES.js) | ||||
|             } | ||||
|             break | ||||
|  | @ -265,25 +244,14 @@ export default async function createServer(root = '', conf = {}) { | |||
|                 return | ||||
|               } | ||||
|               code = compileScss(file) | ||||
|               for (let fn of plugin) { | ||||
|                 code = await fn('css', code) | ||||
|               } | ||||
|               res.setHeader('content-type', MIME_TYPES.css) | ||||
|             } | ||||
|             break | ||||
| 
 | ||||
|           case 'js': | ||||
|           case 'wasm': | ||||
|             { | ||||
|               let rpath = pathname.replace('@/', '') | ||||
|               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) { | ||||
|                 // 判断前后2个值相等, 避免出现目录名和页面名字相同时走错逻辑
 | ||||
|  | @ -299,43 +267,20 @@ export default async function createServer(root = '', conf = {}) { | |||
|               if (fs.isfile(file)) { | ||||
|                 code = fs.cat(file) | ||||
|               } else if (fs.isfile(join(PUBLIC_DIR, rpath))) { | ||||
|                 file = join(PUBLIC_DIR, rpath) | ||||
|                 code = fs.cat(file) | ||||
|                 code = fs.cat(join(PUBLIC_DIR, rpath)) | ||||
|               } else { | ||||
|                 friendlyErrors(rpath, ext) | ||||
|                 res.writeHead(404, USE_HTTPS ? void 0 : 'Not Found') | ||||
|                 res.end('') | ||||
|                 return | ||||
|               } | ||||
|               if (isJson) { | ||||
|                 try { | ||||
|                   code = | ||||
|                     'export default ' + JSON.stringify(JSON.parse(code + '')) | ||||
|                 } catch (err) { | ||||
|                   console.log('%s 语法错误: %s', rpath, red(err.message)) | ||||
|                 } | ||||
|               } 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]) | ||||
|               code = parseJs(code + '', conf.imports, { | ||||
|                 IS_MPA, | ||||
|                 currentPage, | ||||
|                 DEPLOY_PATH, | ||||
|                 LEGACY_MODE | ||||
|               }) | ||||
|               res.setHeader('content-type', MIME_TYPES.js) | ||||
|             } | ||||
| 
 | ||||
|             break | ||||
|  | @ -414,7 +359,7 @@ export default async function createServer(root = '', conf = {}) { | |||
|       ) | ||||
|       chokidar | ||||
|         .watch([SOURCE_DIR, PUBLIC_DIR, join(root, './index.html')]) | ||||
|         .on('all', async (act, filePath) => { | ||||
|         .on('all', (act, filePath) => { | ||||
|           if (ready) { | ||||
|             let file = filePath.slice(SOURCE_DIR.length) | ||||
| 
 | ||||
|  | @ -434,9 +379,6 @@ export default async function createServer(root = '', conf = {}) { | |||
|                     } else { | ||||
|                       content = fs.cat(filePath).toString() | ||||
|                     } | ||||
|                     for (let fn of plugin) { | ||||
|                       content = await fn('css', content) | ||||
|                     } | ||||
|                     ws.send({ | ||||
|                       action: 'render', | ||||
|                       data: { path: file.replace(/\\/g, '/'), content } | ||||
|  | @ -446,7 +388,7 @@ export default async function createServer(root = '', conf = {}) { | |||
| 
 | ||||
|                 case 'vue': | ||||
|                   { | ||||
|                     let content = await compileVue(filePath, conf.imports, { | ||||
|                     let content = compileVue(filePath, conf.imports, { | ||||
|                       IS_MPA, | ||||
|                       currentPage, | ||||
|                       SOURCE_DIR, | ||||
|  | @ -454,9 +396,7 @@ export default async function createServer(root = '', conf = {}) { | |||
|                       DEPLOY_PATH, | ||||
|                       INJECT_SCSS, | ||||
|                       LEGACY_MODE, | ||||
|                       isCustomElement, | ||||
|                       plugin, | ||||
|                       define | ||||
|                       isCustomElement | ||||
|                     }) | ||||
|                     let tmp = CACHE[filePath] | ||||
|                     if (tmp.changed) { | ||||
|  |  | |||
							
								
								
									
										110
									
								
								lib/prod.js
								
								
								
								
							
							
						
						
									
										110
									
								
								lib/prod.js
								
								
								
								
							|  | @ -3,14 +3,10 @@ import { Worker, parentPort } from 'node:worker_threads' | |||
| import os from 'node:os' | ||||
| import fs from 'iofs' | ||||
| 
 | ||||
| import { compileFiles } from './compile.js' | ||||
| import { defaultCustomElement } from './utils.js' | ||||
| 
 | ||||
| const IS_WIN = process.platform === 'win32' | ||||
| const PREFIX = IS_WIN ? 'pages\\' : 'pages/' | ||||
| // 4核(或4线程)以上的CPU, 才开启多线程编译。且线程开销太高, 开太多线程效率反而不高。
 | ||||
| const CPU_CORES = os.cpus().length > 5 ? 6 : os.cpus().length | ||||
| const THREADS_NUM = CPU_CORES > 3 ? CPU_CORES - 1 : 0 | ||||
| // 线程太多, 效率反而不高
 | ||||
| const THREADS_NUM = os.cpus().length > 4 ? 4 : os.cpus().length - 1 | ||||
| const __filename = normalize(import.meta.url.slice(IS_WIN ? 8 : 7)) | ||||
| const __dirname = dirname(__filename) | ||||
| 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 LEGACY_MODE = !!conf.legacy | ||||
|   const { | ||||
|     ABS_CONFIG_FILEPATH, | ||||
|     compileOptions = {}, | ||||
|     define = {}, | ||||
|     plugin = [] | ||||
|   } = conf | ||||
|   const { isCustomElement = defaultCustomElement } = compileOptions | ||||
|   const { isCustomElement } = conf.compileOptions || {} | ||||
| 
 | ||||
|   conf.inject = conf.inject || { scss: '' } | ||||
| 
 | ||||
|  | @ -71,8 +61,22 @@ export default function compile(root = '', dist = '', conf = {}, verbose) { | |||
|     DEPLOY_PATH, | ||||
|     INJECT_SCSS, | ||||
|     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 => { | ||||
|  | @ -93,34 +97,14 @@ export default function compile(root = '', dist = '', conf = {}, verbose) { | |||
|         return | ||||
|       } | ||||
| 
 | ||||
|       if (path === conf.inject.scss) { | ||||
|       if (it.path === conf.inject.scss) { | ||||
|         return | ||||
|       } | ||||
| 
 | ||||
|       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)) { | ||||
|     console.log('\n正在处理静态资源 ...') | ||||
|  | @ -157,35 +141,18 @@ export default function compile(root = '', dist = '', conf = {}, verbose) { | |||
|         list.delete(path) | ||||
|         files.set(path, { name, ext }) | ||||
|       }) | ||||
| 
 | ||||
|       if (THREADS_NUM > 0) { | ||||
|         chunk.set(currentPage, { page, files }) | ||||
|         JOBS_QUEUE.push(chunk) | ||||
|         doJob() | ||||
|       } else { | ||||
|         console.log(`正在生成 ${currentPage}.html ...`) | ||||
|         compileFiles(currentPage, page, files, options, { | ||||
|           verbose, | ||||
|           dist, | ||||
|           imports: conf.imports | ||||
|         }) | ||||
|       } | ||||
|       chunk.set(currentPage, { page, files }) | ||||
|       JOBS_QUEUE.push(chunk) | ||||
|       doJob() | ||||
|     } | ||||
| 
 | ||||
|     // 公共依赖
 | ||||
|     if (THREADS_NUM > 0) { | ||||
|     { | ||||
|       let chunk = new Map() | ||||
|       chunk.set('', { page: null, files: list }) | ||||
| 
 | ||||
|       JOBS_QUEUE.push(chunk) | ||||
|       doJob() | ||||
|     } else { | ||||
|       console.log('\n正在解析公共依赖 ...') | ||||
|       compileFiles('', null, list, options, { | ||||
|         verbose, | ||||
|         dist, | ||||
|         imports: conf.imports | ||||
|       }) | ||||
|     } | ||||
|   } else { | ||||
|     // 每个线程处理的文件数
 | ||||
|  | @ -193,28 +160,19 @@ export default function compile(root = '', dist = '', conf = {}, verbose) { | |||
|     let currentPage = PAGES_KEYS[0] | ||||
|     let page = conf.pages[currentPage] | ||||
| 
 | ||||
|     list = [...list] | ||||
| 
 | ||||
|     console.log(`正在生成 ${currentPage}.html ...`) | ||||
| 
 | ||||
|     if (THREADS_NUM > 0 && list.size > THREADS_NUM * 10) { | ||||
|       list = [...list] | ||||
|       for (let i = 0; i < THREADS_NUM; i++) { | ||||
|         let start = i * chunkSize | ||||
|         let end = start + chunkSize | ||||
|         let chunk = new Map() | ||||
|     for (let i = 0; i < THREADS_NUM; i++) { | ||||
|       let start = i * chunkSize | ||||
|       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) | ||||
|         doJob() | ||||
|       } | ||||
|     } else { | ||||
|       options.plugin = plugin | ||||
|       options.isCustomElement = isCustomElement | ||||
|       compileFiles(currentPage, page, list, options, { | ||||
|         verbose, | ||||
|         dist, | ||||
|         imports: conf.imports | ||||
|       }) | ||||
|       JOBS_QUEUE.push(chunk) | ||||
|       doJob() | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -8,17 +8,11 @@ import { compileFiles } from './compile.js' | |||
| import { defaultCustomElement } from './utils.js' | ||||
| 
 | ||||
| const { options, verbose, dist, imports } = workerData | ||||
| const { ABS_CONFIG_FILEPATH } = options | ||||
| 
 | ||||
| const { compileOptions = {}, plugin = [] } = await import( | ||||
|   ABS_CONFIG_FILEPATH | ||||
| ).then(r => r.default) | ||||
| options.isCustomElement = options.isCustomElement | ||||
|   ? Function('return ' + options.isCustomElement)() | ||||
|   : defaultCustomElement | ||||
| 
 | ||||
| const { isCustomElement = defaultCustomElement } = compileOptions | ||||
| options.isCustomElement = isCustomElement | ||||
| options.plugin = plugin | ||||
| 
 | ||||
| //
 | ||||
| async function doJob(job) { | ||||
|   let [currentPage, { page, files }] = job.entries().next().value | ||||
| 
 | ||||
|  | @ -34,7 +28,7 @@ async function doJob(job) { | |||
|     dist, | ||||
|     imports | ||||
|   }) | ||||
|   parentPort.postMessage(true) | ||||
|   parentPort.postMessage('ok') | ||||
| } | ||||
| 
 | ||||
| parentPort.on('message', doJob) | ||||
|  |  | |||
|  | @ -8,7 +8,6 @@ import { createHash, randomUUID } from 'node:crypto' | |||
| import { join } from 'node:path' | ||||
| import { gzipSync } from 'node:zlib' | ||||
| import { red, cyan, blue } from 'kolorist' | ||||
| import { LEGACY_POLYFILL } from './constants.js' | ||||
| 
 | ||||
| // 修正路径合并 避免在windows下被转义
 | ||||
| export function urlJoin(...args) { | ||||
|  | @ -40,7 +39,7 @@ export function friendlyErrors(pathname, ext = '') { | |||
| export function createHmrScript(legacy, session = '') { | ||||
|   return ` | ||||
|   !(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) { | ||||
|       if(vue_live_hmr.closed){ | ||||
|  | @ -52,9 +51,6 @@ export function createHmrScript(legacy, session = '') { | |||
|    | ||||
|     ws.addEventListener('close', function(){ | ||||
|       vue_live_hmr.closed = true | ||||
|       if (localStorage.getItem(location.pathname) === '1') { | ||||
|         return | ||||
|       } | ||||
|       setTimeout(vue_live_hmr, 2000) | ||||
|     }) | ||||
|      | ||||
|  | @ -94,7 +90,6 @@ export function createHmrScript(legacy, session = '') { | |||
|           break | ||||
|       } | ||||
|     }) | ||||
|     ${LEGACY_POLYFILL} | ||||
|   })() | ||||
|   ` | ||||
| } | ||||
|  |  | |||
							
								
								
									
										19
									
								
								lib/ws.js
								
								
								
								
							
							
						
						
									
										19
									
								
								lib/ws.js
								
								
								
								
							|  | @ -15,21 +15,16 @@ class WebSocket { | |||
|       conn.on('connection', (client, req) => { | ||||
|         let params = new URLSearchParams(req.url.slice(req.url.indexOf('?'))) | ||||
|         let session = params.get('session') | ||||
|         let lock = +params.get('lock') | ||||
| 
 | ||||
|         if (lock === 1) { | ||||
|           client.close() | ||||
|         } else { | ||||
|           this.#clients.set(session, client) | ||||
|         this.#clients.set(session, client) | ||||
| 
 | ||||
|           client.once('close', _ => { | ||||
|             this.#clients.delete(session) | ||||
|           }) | ||||
|         client.once('close', _ => { | ||||
|           this.#clients.delete(session) | ||||
|         }) | ||||
| 
 | ||||
|           while (this.#queue.length) { | ||||
|             let msg = this.#queue.shift() | ||||
|             this.send(msg) | ||||
|           } | ||||
|         while (this.#queue.length) { | ||||
|           let msg = this.#queue.shift() | ||||
|           this.send(msg) | ||||
|         } | ||||
|       }) | ||||
|     } | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| { | ||||
|   "name": "fite", | ||||
|   "type": "module", | ||||
|   "version": "1.4.4", | ||||
|   "version": "1.1.10", | ||||
|   "bin": { | ||||
|     "fite": "index.js" | ||||
|   }, | ||||
|  | @ -19,6 +19,5 @@ | |||
|   }, | ||||
|   "engines": { | ||||
|     "node": ">=16.6.0" | ||||
|   }, | ||||
|   "license": "MIT" | ||||
|   } | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue