Compare commits
	
		
			79 Commits 
		
	
	
	| Author | SHA1 | Date | 
|---|---|---|
| 
            
            
              
               | 
          2164cb0137 | |
| 
            
            
              
               | 
          96b9554574 | |
| 
            
            
              
               | 
          dd8ff91294 | |
| 
            
            
              
               | 
          fa84cc0c5d | |
| 
            
            
              
               | 
          2b9599781e | |
| 
            
            
              
               | 
          7167a8b467 | |
| 
            
            
              
               | 
          c3dd7a843c | |
| 
            
            
              
               | 
          d84a59a5e5 | |
| 
            
            
              
               | 
          66925dfbf2 | |
| 
            
            
              
               | 
          a15bd9ea95 | |
| 
            
            
              
               | 
          100c372718 | |
| 
            
            
              
               | 
          bf68560ae5 | |
| 
            
            
              
               | 
          24aea78d3d | |
| 
            
            
              
               | 
          6ca2d7aee0 | |
| 
            
            
              
               | 
          0a05548c15 | |
| 
            
            
              
               | 
          ce34b01f68 | |
| 
            
            
              
               | 
          00a421d728 | |
| 
            
            
              
               | 
          da7649d362 | |
| 
            
            
              
               | 
          0225e1e499 | |
| 
            
            
              
               | 
          da70bcc685 | |
| 
            
            
              
               | 
          3404c87a71 | |
| 
            
            
              
               | 
          1b3425196e | |
| 
            
            
              
               | 
          d8a783a336 | |
| 
            
            
              
               | 
          f4a3ff6355 | |
| 
            
            
              
               | 
          9e34077754 | |
| 
            
            
              
               | 
          21df0b438b | |
| 
            
            
              
               | 
          c414dbe549 | |
| 
            
            
              
               | 
          ea986f0a39 | |
| 
            
            
              
               | 
          cca39d9156 | |
| 
            
            
              
               | 
          82c8686bfb | |
| 
            
            
              
               | 
          50a54a152c | |
| 
            
            
              
               | 
          ff5a225cc1 | |
| 
            
            
              
               | 
          c675b73f25 | |
| 
            
            
              
               | 
          3c239fe721 | |
| 
            
            
              
               | 
          f08361a986 | |
| 
            
            
              
               | 
          6580ab6837 | |
| 
            
            
              
               | 
          f150cbe0ff | |
| 
            
            
              
               | 
          aba20ae873 | |
| 
            
            
              
               | 
          9966e3ac9a | |
| 
            
            
              
               | 
          6af38aad87 | |
| 
            
            
              
               | 
          239ebe6cf3 | |
| 
            
            
              
               | 
          a10ebac085 | |
| 
            
            
              
               | 
          24a450ffe8 | |
| 
            
            
              
               | 
          73ff200b87 | |
| 
            
            
              
               | 
          45ac8e8b3d | |
| 
            
            
              
               | 
          28838bb848 | |
| 
            
            
              
               | 
          3385c4947a | |
| 
            
            
              
               | 
          388465842c | |
| 
            
            
              
               | 
          e6af6842a5 | |
| 
            
            
              
               | 
          ba63cd14ba | |
| 
            
            
              
               | 
          f09e6199ac | |
| 
            
            
              
               | 
          877ba7c8bf | |
| 
            
            
              
               | 
          1a56f37879 | |
| 
            
            
              
               | 
          7d9e691232 | |
| 
            
            
              
               | 
          ad12433785 | |
| 
            
            
              
               | 
          27afea8649 | |
| 
            
            
              
               | 
          7ed5f81da2 | |
| 
            
            
              
               | 
          5cd075c46e | |
| 
            
            
              
               | 
          8c26342cd5 | |
| 
            
            
              
               | 
          1265d0e94f | |
| 
            
            
              
               | 
          97a10f3235 | |
| 
            
            
              
               | 
          927aaa5bc9 | |
| 
            
            
              
               | 
          5d9f768f1e | |
| 
            
            
              
               | 
          99a5fac97d | |
| 
            
            
              
               | 
          472aa4d116 | |
| 
            
            
              
               | 
          0b398f4d67 | |
| 
            
            
              
               | 
          6a6a8e31c6 | |
| 
            
            
              
               | 
          0af6b36027 | |
| 
            
            
              
               | 
          3e6ecc5e5f | |
| 
            
            
              
               | 
          b08b5c9172 | |
| 
            
            
              
               | 
          f28212f933 | |
| 
            
            
              
               | 
          d1e1084ec1 | |
| 
            
            
              
               | 
          d5dcd44fec | |
| 
            
            
              
               | 
          574dd7e693 | |
| 
            
            
              
               | 
          dc1fea1bbb | |
| 
            
            
              
               | 
          a0f06b0171 | |
| 
            
            
              
               | 
          d80fd97601 | |
| 
            
            
              
               | 
          5b3a976fd3 | |
| 
            
            
              
               | 
          e5a76f21ab | 
| 
						 | 
					@ -0,0 +1,10 @@
 | 
				
			||||||
 | 
					jsxBracketSameLine: true
 | 
				
			||||||
 | 
					jsxSingleQuote: true
 | 
				
			||||||
 | 
					semi: false
 | 
				
			||||||
 | 
					singleQuote: true
 | 
				
			||||||
 | 
					printWidth: 80
 | 
				
			||||||
 | 
					useTabs: false
 | 
				
			||||||
 | 
					tabWidth: 2
 | 
				
			||||||
 | 
					trailingComma: none
 | 
				
			||||||
 | 
					bracketSpacing: true
 | 
				
			||||||
 | 
					arrowParens: avoid
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,21 @@
 | 
				
			||||||
 | 
					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()`。
 | 
					- 因为没有内置完整的样式处理,支持`scoped`、`:deep()`, 但不支持`:global()`。
 | 
				
			||||||
- `单文件组件`中的样式, 如果是用scss, 不支持引用其他文件, 也不支持设置共用定义文件。
 | 
					- `单文件组件`中的样式, 如果是用scss, 不支持引用其他文件。
 | 
				
			||||||
- 样式预处理器, 只支持scss, 不支持less。
 | 
					- 样式预处理器, 只支持scss, 不支持less。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										34
									
								
								index.js
								
								
								
								
							
							
						
						
									
										34
									
								
								index.js
								
								
								
								
							| 
						 | 
					@ -7,7 +7,7 @@
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import fs from 'iofs'
 | 
					import fs from 'iofs'
 | 
				
			||||||
import { join, normalize } from 'path'
 | 
					import { join, normalize } from 'node:path'
 | 
				
			||||||
import { red, blue } from 'kolorist'
 | 
					import { red, blue } from 'kolorist'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import createServer from './lib/dev.js'
 | 
					import createServer from './lib/dev.js'
 | 
				
			||||||
| 
						 | 
					@ -18,14 +18,18 @@ 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('.').slice(0, 2).join('.')
 | 
					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 clean = !args.includes('--no-clean') //
 | 
				
			||||||
 | 
					let verbose = args.includes('--verbose') //
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if (NODE_VERSION < 16.6) {
 | 
					if (NODE_VERSION[0] < 16 || (NODE_VERSION[0] === 16 && NODE_VERSION[1] < 6)) {
 | 
				
			||||||
  console.log(red('Error: 你当前的环境不满足 Vue-live 构建工具的要求'))
 | 
					  console.log(red('Error: 你当前的环境不满足 fite 构建工具的要求'))
 | 
				
			||||||
  console.log(
 | 
					  console.log(
 | 
				
			||||||
    'Vue-live 需要Node.js版本在 %s 以上, \n你当前的Node.js版本为: %s',
 | 
					    'fite 需要Node.js版本在 %s 以上, \n你当前的Node.js版本为: %s',
 | 
				
			||||||
    blue('v16.6.0'),
 | 
					    blue('v16.6.0'),
 | 
				
			||||||
    red(process.version),
 | 
					    red(process.version),
 | 
				
			||||||
    '\n\n'
 | 
					    '\n\n'
 | 
				
			||||||
| 
						 | 
					@ -33,10 +37,13 @@ if (NODE_VERSION < 16.6) {
 | 
				
			||||||
  process.exit()
 | 
					  process.exit()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
switch (args[0]) {
 | 
					switch (mode) {
 | 
				
			||||||
  case 'dev':
 | 
					  case 'dev':
 | 
				
			||||||
    import(PROTOCOL + CONFIG_FILE)
 | 
					    process.env.NODE_ENV = 'development'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    import(ABS_CONFIG_FILEPATH)
 | 
				
			||||||
      .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 => {
 | 
				
			||||||
| 
						 | 
					@ -45,14 +52,17 @@ switch (args[0]) {
 | 
				
			||||||
    break
 | 
					    break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  case 'build':
 | 
					  case 'build':
 | 
				
			||||||
    import(PROTOCOL + CONFIG_FILE)
 | 
					    process.env.NODE_ENV = 'production'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    import(ABS_CONFIG_FILEPATH)
 | 
				
			||||||
      .then(function (conf) {
 | 
					      .then(function (conf) {
 | 
				
			||||||
        let dist = conf.buildDir || 'dist'
 | 
					        let dist = conf.buildDir || 'dist'
 | 
				
			||||||
        if (fs.isdir(dist)) {
 | 
					        if (clean && fs.isdir(dist)) {
 | 
				
			||||||
          fs.rm(dist, true)
 | 
					          console.log('清除dist目录...')
 | 
				
			||||||
 | 
					          fs.rm(dist)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        fs.mkdir(dist)
 | 
					        conf.default.ABS_CONFIG_FILEPATH = ABS_CONFIG_FILEPATH
 | 
				
			||||||
        compile(WORK_SPACE, dist, conf.default)
 | 
					        compile(WORK_SPACE, dist, conf.default, verbose)
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .catch(err => {
 | 
					      .catch(err => {
 | 
				
			||||||
        console.log(err)
 | 
					        console.log(err)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,36 +6,43 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import fs from 'iofs'
 | 
					import fs from 'iofs'
 | 
				
			||||||
import scss from '@bytedo/sass'
 | 
					import scss from '@bytedo/sass'
 | 
				
			||||||
import { createHash } from 'crypto'
 | 
					 | 
				
			||||||
import Es from 'esbuild'
 | 
					import Es from 'esbuild'
 | 
				
			||||||
import { join } from 'path'
 | 
					
 | 
				
			||||||
import { compile } from '@vue/compiler-dom'
 | 
					import { compile } from '@vue/compiler-dom'
 | 
				
			||||||
 | 
					import { red, cyan, blue } from 'kolorist'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  JS_EXP,
 | 
					  JS_EXP,
 | 
				
			||||||
  STYLE_EXP,
 | 
					  STYLE_EXP,
 | 
				
			||||||
  HTML_EXP,
 | 
					  HTML_EXP,
 | 
				
			||||||
  CSS_SHEET_EXP,
 | 
					  CSS_SHEET_EXP,
 | 
				
			||||||
  HMR_SCRIPT,
 | 
					  V_DEEP,
 | 
				
			||||||
  V_DEEP
 | 
					  PERCENT_EXP,
 | 
				
			||||||
 | 
					  SHEETS_DEF,
 | 
				
			||||||
 | 
					  LEGACY_POLYFILL
 | 
				
			||||||
} from './constants.js'
 | 
					} from './constants.js'
 | 
				
			||||||
 | 
					import { createHmrScript, md5, uuid, urlJoin } from './utils.js'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const OPTIONS = {
 | 
					const OPTIONS = {
 | 
				
			||||||
  indentType: 'space',
 | 
					  style: 'compressed'
 | 
				
			||||||
  indentWidth: 2
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 修正路径合并 避免在windows下被转义
 | 
					function minify(code) {
 | 
				
			||||||
function urlJoin(...args) {
 | 
					  return Es.transformSync(code, { minify: true }).code.trim()
 | 
				
			||||||
  return join(...args).replace(/\\/g, '/')
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function md5(str = '') {
 | 
					// 处理css中的 :deep()
 | 
				
			||||||
  let sum = createHash('md5')
 | 
					function parseVDeep(curr, val, scoped) {
 | 
				
			||||||
  sum.update(str, 'utf8')
 | 
					  let res = V_DEEP.exec(curr)
 | 
				
			||||||
  return sum.digest('hex').slice(0, 8)
 | 
					  if (res) {
 | 
				
			||||||
 | 
					    scoped && (val = val.replace(/\[data\-[^\]]+\]/g, ''))
 | 
				
			||||||
 | 
					    return `${res[1] + res[2]} ${val}`
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    return `${curr} ${val}`
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 处理style中的scoped属性
 | 
				
			||||||
function scopeCss(css = '', hash) {
 | 
					function scopeCss(css = '', hash) {
 | 
				
			||||||
  return css.replace(CSS_SHEET_EXP, (m, selector) => {
 | 
					  return css.replace(CSS_SHEET_EXP, (m, selector) => {
 | 
				
			||||||
    if (!selector.startsWith('@')) {
 | 
					    if (!selector.startsWith('@')) {
 | 
				
			||||||
| 
						 | 
					@ -43,26 +50,39 @@ function scopeCss(css = '', hash) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      selector = selector
 | 
					      selector = selector
 | 
				
			||||||
        .map(s => {
 | 
					        .map(s => {
 | 
				
			||||||
 | 
					          // 针对 @keyframe的处理
 | 
				
			||||||
 | 
					          if (s === 'from' || s === 'to' || PERCENT_EXP.test(s)) {
 | 
				
			||||||
 | 
					            return s
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
          let tmp = s.split(' ')
 | 
					          let tmp = s.split(' ')
 | 
				
			||||||
          let last = tmp.pop()
 | 
					          let output = ''
 | 
				
			||||||
          if (last.includes(':')) {
 | 
					          let last
 | 
				
			||||||
            let res = V_DEEP.exec(last)
 | 
					          let scoped = false
 | 
				
			||||||
            if (res) {
 | 
					
 | 
				
			||||||
              last = tmp.pop()
 | 
					          while ((last = tmp.pop())) {
 | 
				
			||||||
              last += `[data-${hash}] ` + res[1]
 | 
					            if (scoped) {
 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
              if (last.startsWith(':')) {
 | 
					              if (last.startsWith(':')) {
 | 
				
			||||||
                let _prev = tmp.pop()
 | 
					                output = parseVDeep(last, output, true)
 | 
				
			||||||
                last = `${_prev}[data-${hash}] ` + last
 | 
					 | 
				
			||||||
              } else {
 | 
					              } else {
 | 
				
			||||||
                last = last.replace(':', `[data-${hash}]:`)
 | 
					                output = `${last}[data-${hash}] ${output}`
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              if (last.includes(':')) {
 | 
				
			||||||
 | 
					                if (last.startsWith(':')) {
 | 
				
			||||||
 | 
					                  output = parseVDeep(last, output)
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                  scoped = true
 | 
				
			||||||
 | 
					                  last = last.replace(':', `[data-${hash}]:`)
 | 
				
			||||||
 | 
					                  output = `${last} ${output}`
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					              } else {
 | 
				
			||||||
 | 
					                scoped = true
 | 
				
			||||||
 | 
					                output = `${last}[data-${hash}] ${output}`
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          } else {
 | 
					 | 
				
			||||||
            last += `[data-${hash}]`
 | 
					 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          tmp.push(last)
 | 
					
 | 
				
			||||||
          return tmp.join(' ')
 | 
					          return output
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        .join(', ')
 | 
					        .join(', ')
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -73,18 +93,16 @@ function scopeCss(css = '', hash) {
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 编译scss为css
 | 
					 * 编译scss为css
 | 
				
			||||||
 * @param file <String> 文件路径或scss代码
 | 
					 * @param file <String> 文件路径或scss代码
 | 
				
			||||||
 * @param mini <Boolean> 是否压缩
 | 
					 * @param inject <String> 要注入的scss代码
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export function compileScss(file, mini = true) {
 | 
					export function compileScss(file, inject = '') {
 | 
				
			||||||
  let style = mini ? 'compressed' : 'expanded'
 | 
					 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    if (fs.isfile(file)) {
 | 
					    if (fs.isfile(file)) {
 | 
				
			||||||
      return scss.compile(file, { style, ...OPTIONS }).css.trim()
 | 
					      return scss.compile(file, OPTIONS).css.trim()
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      return scss.compileString(file, { style, ...OPTIONS }).css.trim()
 | 
					      return scss.compileString(inject + file, OPTIONS).css.trim()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  } catch (err) {
 | 
					  } catch (err) {
 | 
				
			||||||
    console.log('compile scss: ', file)
 | 
					 | 
				
			||||||
    console.error(err)
 | 
					    console.error(err)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -97,113 +115,194 @@ export function compileScss(file, mini = true) {
 | 
				
			||||||
export function parseJs(
 | 
					export function parseJs(
 | 
				
			||||||
  code = '',
 | 
					  code = '',
 | 
				
			||||||
  imports,
 | 
					  imports,
 | 
				
			||||||
  { IS_MPA, currentPage, IS_ENTRY, DEPLOY_PATH } = {},
 | 
					  { IS_MPA, currentPage, IS_ENTRY, DEPLOY_PATH, LEGACY_MODE, define } = {},
 | 
				
			||||||
  isBuild
 | 
					  filename,
 | 
				
			||||||
 | 
					  linePatch = 1
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
  let fixedStyle = '\n\n'
 | 
					  let fixedStyle = ''
 | 
				
			||||||
  let ASSETS_DIR = '/@/'
 | 
					  let ASSETS_DIR = '/@/'
 | 
				
			||||||
 | 
					  let isBuild = process.env.NODE_ENV === 'production'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (isBuild) {
 | 
					  if (isBuild) {
 | 
				
			||||||
    ASSETS_DIR = '/assets/' // + (IS_MPA ? 'pages/' : '')
 | 
					    ASSETS_DIR = '/assets/'
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  code = Es.transformSync(code).code || ''
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  try {
 | 
				
			||||||
    code
 | 
					    code = Es.transformSync(code).code || ''
 | 
				
			||||||
      .replace(/\r\n/g, '\n')
 | 
					  } catch (e) {
 | 
				
			||||||
      .replace(
 | 
					    let err = e.errors.pop()
 | 
				
			||||||
        /import ([\w\W]*?) from (["'])(.*?)\2/g,
 | 
					    let lines = code.split('\n')
 | 
				
			||||||
        function (m, alias, q, name) {
 | 
					 | 
				
			||||||
          if (name.startsWith('@/')) {
 | 
					 | 
				
			||||||
            name = name.replace('@/', urlJoin(DEPLOY_PATH, ASSETS_DIR))
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
          if (!imports[name]) {
 | 
					    console.log('%s: %s', red('Uncaught SyntaxError'), red(err.text))
 | 
				
			||||||
            if (name.startsWith('./') || name.startsWith('../')) {
 | 
					    // 将上下文几行都打印出来
 | 
				
			||||||
              if (IS_ENTRY) {
 | 
					    for (let i = err.location.line - 3; i <= err.location.line + 1; i++) {
 | 
				
			||||||
                if (IS_MPA) {
 | 
					      console.log(
 | 
				
			||||||
                  name = `pages/${currentPage}/` + name
 | 
					        '  @ line %d: %s',
 | 
				
			||||||
                }
 | 
					        i + linePatch,
 | 
				
			||||||
                name = urlJoin(DEPLOY_PATH, ASSETS_DIR, name)
 | 
					        err.location.line === i + 1 ? red(lines[i]) : lines[i]
 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
            } else if (
 | 
					 | 
				
			||||||
              name.startsWith('/') &&
 | 
					 | 
				
			||||||
              !name.startsWith('//') &&
 | 
					 | 
				
			||||||
              !name.startsWith(urlJoin(DEPLOY_PATH, ASSETS_DIR))
 | 
					 | 
				
			||||||
            ) {
 | 
					 | 
				
			||||||
              name = name.replace(/^\//, urlJoin(DEPLOY_PATH, ASSETS_DIR))
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (!name.endsWith('.js') && !name.endsWith('.vue')) {
 | 
					 | 
				
			||||||
              if (name.includes('components')) {
 | 
					 | 
				
			||||||
                name += '.vue'
 | 
					 | 
				
			||||||
              } else {
 | 
					 | 
				
			||||||
                name += '.js'
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          if (isBuild) {
 | 
					 | 
				
			||||||
            name = name.replace(/\.vue$/, '.js')
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          return `import ${alias} from '${name}'`
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      )
 | 
					      )
 | 
				
			||||||
      .replace(/import\((['"])(.*?)\1\)/g, function (m, q, name) {
 | 
					    }
 | 
				
			||||||
        if (isBuild) {
 | 
					    console.log(
 | 
				
			||||||
          name = name.replace(/\.vue$/, '.js')
 | 
					      '  @ %s:%d:%d',
 | 
				
			||||||
        }
 | 
					      blue(filename),
 | 
				
			||||||
 | 
					      err.location.line + linePatch - 1,
 | 
				
			||||||
 | 
					      err.location.column
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  code = code
 | 
				
			||||||
 | 
					    .replace(/\r\n/g, '\n')
 | 
				
			||||||
 | 
					    .replace(/process\.env\.NODE_ENV/g, `'${process.env.NODE_ENV}'`)
 | 
				
			||||||
 | 
					    .replace(
 | 
				
			||||||
 | 
					      /(import|export) ([^'"]*?) from (["'])(.*?)\3/g,
 | 
				
			||||||
 | 
					      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))
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return `import('${name}')`
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .replace(/import (["'])(.*?)\1/g, function (m, q, name) {
 | 
					 | 
				
			||||||
        if (name.endsWith('.css') || name.endsWith('.scss')) {
 | 
					 | 
				
			||||||
          if (name.startsWith('@/')) {
 | 
					 | 
				
			||||||
            name = name.replace('@/', '/')
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
          if (isBuild) {
 | 
					        if (!imports[name]) {
 | 
				
			||||||
            name = name.replace(/\.scss/, '.css')
 | 
					          if (name.startsWith('./') || name.startsWith('../')) {
 | 
				
			||||||
          }
 | 
					            if (IS_ENTRY) {
 | 
				
			||||||
          let tmp = `style${Date.now()}`
 | 
					              if (IS_MPA) {
 | 
				
			||||||
          fixedStyle += `document.adoptedStyleSheets.push(${tmp})\n`
 | 
					                name = `pages/${currentPage}/` + name
 | 
				
			||||||
 | 
					 | 
				
			||||||
          // 修正那反人类的windows路径
 | 
					 | 
				
			||||||
          return `import ${tmp} from '${name}' assert { type: 'css' }\n${tmp}.path = '${name.replace(
 | 
					 | 
				
			||||||
            /\\/g,
 | 
					 | 
				
			||||||
            '/'
 | 
					 | 
				
			||||||
          )}'`
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          if (name.startsWith('@/')) {
 | 
					 | 
				
			||||||
            name = name.replace('@/', urlJoin(DEPLOY_PATH, ASSETS_DIR))
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          //
 | 
					 | 
				
			||||||
          if (!imports[name]) {
 | 
					 | 
				
			||||||
            if (name.startsWith('./') || name.startsWith('../')) {
 | 
					 | 
				
			||||||
              if (IS_ENTRY) {
 | 
					 | 
				
			||||||
                if (IS_MPA) {
 | 
					 | 
				
			||||||
                  name = `${currentPage}/` + name
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                name = urlJoin(DEPLOY_PATH, ASSETS_DIR, name)
 | 
					 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            } else if (
 | 
					              name = urlJoin(DEPLOY_PATH, ASSETS_DIR, name)
 | 
				
			||||||
              name.startsWith('/') &&
 | 
					 | 
				
			||||||
              !name.startsWith('//') &&
 | 
					 | 
				
			||||||
              !name.startsWith(urlJoin(DEPLOY_PATH, ASSETS_DIR))
 | 
					 | 
				
			||||||
            ) {
 | 
					 | 
				
			||||||
              name = name.replace(/^\//, urlJoin(DEPLOY_PATH, ASSETS_DIR))
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					          } else if (
 | 
				
			||||||
 | 
					            name.startsWith('/') &&
 | 
				
			||||||
 | 
					            !name.startsWith('//') &&
 | 
				
			||||||
 | 
					            !name.startsWith(urlJoin(DEPLOY_PATH, ASSETS_DIR))
 | 
				
			||||||
 | 
					          ) {
 | 
				
			||||||
 | 
					            name = name.replace(/^\//, urlJoin(DEPLOY_PATH, ASSETS_DIR))
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (!name.endsWith('.js') && !name.endsWith('.vue')) {
 | 
					          if (
 | 
				
			||||||
 | 
					            !name.endsWith('.js') &&
 | 
				
			||||||
 | 
					            !name.endsWith('.mjs') &&
 | 
				
			||||||
 | 
					            !name.endsWith('.vue')
 | 
				
			||||||
 | 
					          ) {
 | 
				
			||||||
 | 
					            if (name.includes('components')) {
 | 
				
			||||||
 | 
					              name += '.vue'
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
              name += '.js'
 | 
					              name += '.js'
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					 | 
				
			||||||
          return `import '${name}'`
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }) + fixedStyle
 | 
					        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
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .replace(/import\((['"])(.*?)\1\)/g, function (m, q, name) {
 | 
				
			||||||
 | 
					      if (isBuild) {
 | 
				
			||||||
 | 
					        name = name.replace(/\.vue$/, '.js')
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      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) {
 | 
				
			||||||
 | 
					      if (name.endsWith('.css') || name.endsWith('.scss')) {
 | 
				
			||||||
 | 
					        if (name.startsWith('@/')) {
 | 
				
			||||||
 | 
					          name = name.replace('@/', urlJoin(DEPLOY_PATH, ASSETS_DIR))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (isBuild) {
 | 
				
			||||||
 | 
					          name = name.replace(/\.scss/, '.css')
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // 修正那反人类的windows路径
 | 
				
			||||||
 | 
					        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})()`
 | 
				
			||||||
 | 
					        } 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`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          return `const ${tmp} = await __fite_import('${name}', import.meta.url)`
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        if (name.startsWith('@/')) {
 | 
				
			||||||
 | 
					          name = name.replace('@/', urlJoin(DEPLOY_PATH, ASSETS_DIR))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
 | 
					        if (!imports[name]) {
 | 
				
			||||||
 | 
					          if (name.startsWith('./') || name.startsWith('../')) {
 | 
				
			||||||
 | 
					            if (IS_ENTRY) {
 | 
				
			||||||
 | 
					              if (IS_MPA) {
 | 
				
			||||||
 | 
					                name = `${currentPage}/` + name
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					              name = urlJoin(DEPLOY_PATH, ASSETS_DIR, name)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          } else if (
 | 
				
			||||||
 | 
					            name.startsWith('/') &&
 | 
				
			||||||
 | 
					            !name.startsWith('//') &&
 | 
				
			||||||
 | 
					            !name.startsWith(urlJoin(DEPLOY_PATH, ASSETS_DIR))
 | 
				
			||||||
 | 
					          ) {
 | 
				
			||||||
 | 
					            name = name.replace(/^\//, urlJoin(DEPLOY_PATH, ASSETS_DIR))
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          if (
 | 
				
			||||||
 | 
					            !name.endsWith('.js') &&
 | 
				
			||||||
 | 
					            !name.endsWith('.mjs') &&
 | 
				
			||||||
 | 
					            !name.endsWith('.vue')
 | 
				
			||||||
 | 
					          ) {
 | 
				
			||||||
 | 
					            name += '.js'
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return `import '${name}'`
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for (let key in define) {
 | 
				
			||||||
 | 
					    code = code.replaceAll(key, define[key])
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (fixedStyle) {
 | 
				
			||||||
 | 
					    code += '\n\n' + (IS_ENTRY ? SHEETS_DEF : '') + fixedStyle
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (IS_ENTRY && !LEGACY_MODE) {
 | 
				
			||||||
 | 
					      code += '\ndocument.adoptedStyleSheets = __sheets__'
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return code
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
| 
						 | 
					@ -211,16 +310,19 @@ export function parseJs(
 | 
				
			||||||
 * @param file <String> 文件路径
 | 
					 * @param file <String> 文件路径
 | 
				
			||||||
 * @return <String> 返回转换后的js代码
 | 
					 * @return <String> 返回转换后的js代码
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export function compileVue(file, imports, options = {}, isBuild) {
 | 
					export async function compileVue(file, imports, options = {}) {
 | 
				
			||||||
 | 
					  // 修正那反人类的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')
 | 
				
			||||||
  let CACHE = options.CACHE || {}
 | 
					  let CACHE = options.CACHE || {}
 | 
				
			||||||
 | 
					  let { isCustomElement } = options
 | 
				
			||||||
  let output = '',
 | 
					  let output = '',
 | 
				
			||||||
    scoped = false
 | 
					    scoped = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  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
 | 
				
			||||||
| 
						 | 
					@ -229,32 +331,68 @@ export function compileVue(file, imports, options = {}, isBuild) {
 | 
				
			||||||
    .map(it => {
 | 
					    .map(it => {
 | 
				
			||||||
      let css
 | 
					      let css
 | 
				
			||||||
      if (it.length > 2) {
 | 
					      if (it.length > 2) {
 | 
				
			||||||
        css = compileScss(it[2])
 | 
					        css = compileScss(it[2], options.INJECT_SCSS)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (it[1].includes('scoped')) {
 | 
					        if (it[1].includes('scoped')) {
 | 
				
			||||||
          scoped = true
 | 
					          scoped = true
 | 
				
			||||||
          css = scopeCss(css, hash)
 | 
					          css = scopeCss(css, hash)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        css = compileScss(it[1])
 | 
					        css = compileScss(it[1], options.INJECT_SCSS)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return css
 | 
					      return css
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    .join(' ')
 | 
					    .join(' ')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  js = js ? js[1] : ''
 | 
					  for (let fn of options.plugin) {
 | 
				
			||||||
 | 
					    scss = await fn('css', scss)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  html = compile(html[1], {
 | 
					  js = js ? js[1] : 'export default {}'
 | 
				
			||||||
    mode: 'module',
 | 
					 | 
				
			||||||
    prefixIdentifiers: true,
 | 
					 | 
				
			||||||
    hoistStatic: true,
 | 
					 | 
				
			||||||
    cacheHandlers: true,
 | 
					 | 
				
			||||||
    scopeId: scoped ? scopeId : void 0,
 | 
					 | 
				
			||||||
    sourceMap: false,
 | 
					 | 
				
			||||||
    isCustomElement: tag => tag.startsWith('wc-')
 | 
					 | 
				
			||||||
  }).code.replace('export function render', 'function render')
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  output += html + '\n\n'
 | 
					  try {
 | 
				
			||||||
 | 
					    html = compile(html[1], {
 | 
				
			||||||
 | 
					      mode: 'module',
 | 
				
			||||||
 | 
					      prefixIdentifiers: true,
 | 
				
			||||||
 | 
					      hoistStatic: true,
 | 
				
			||||||
 | 
					      cacheHandlers: true,
 | 
				
			||||||
 | 
					      scopeId: scoped ? scopeId : void 0,
 | 
				
			||||||
 | 
					      sourceMap: false,
 | 
				
			||||||
 | 
					      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('%s: %s', red('SyntaxError'), red(err.message))
 | 
				
			||||||
 | 
					    console.log(
 | 
				
			||||||
 | 
					      '  @ %s%s%s',
 | 
				
			||||||
 | 
					      line.slice(0, err.loc.start.column - 1),
 | 
				
			||||||
 | 
					      red(err.loc.source),
 | 
				
			||||||
 | 
					      line.slice(err.loc.end.column - 1)
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    console.log(
 | 
				
			||||||
 | 
					      '  @ (%s:%d:%d)\n',
 | 
				
			||||||
 | 
					      file,
 | 
				
			||||||
 | 
					      err.loc.start.line,
 | 
				
			||||||
 | 
					      err.loc.start.column
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    html = `
 | 
				
			||||||
 | 
					import { openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function render(_ctx, _cache) {
 | 
				
			||||||
 | 
					  return (_openBlock(), _createElementBlock("div", null, "SyntaxError: ${err.message}"))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					    `
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  html = html
 | 
				
			||||||
 | 
					    .replace(/import .* from "vue"/, str => {
 | 
				
			||||||
 | 
					      output += str + '\n'
 | 
				
			||||||
 | 
					      return ''
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .trim()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (CACHE[file]) {
 | 
					  if (CACHE[file]) {
 | 
				
			||||||
    CACHE[file] = {
 | 
					    CACHE[file] = {
 | 
				
			||||||
| 
						 | 
					@ -266,21 +404,36 @@ export function compileVue(file, imports, options = {}, isBuild) {
 | 
				
			||||||
    CACHE[file] = { changed: false, js, html }
 | 
					    CACHE[file] = { changed: false, js, html }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  output += parseJs(js, imports, options, isBuild).replace(
 | 
					  output += parseJs(js, imports, options, file, linePatch).replace(
 | 
				
			||||||
    'export default {',
 | 
					    'export default {',
 | 
				
			||||||
    'const __sfc__ = {\n  render,\n'
 | 
					    `\n${
 | 
				
			||||||
 | 
					      options.LEGACY_MODE ? '' : SHEETS_DEF
 | 
				
			||||||
 | 
					    }${html}\n\nconst __sfc__ = {\n  render,\n`
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (scss) {
 | 
					  if (scss) {
 | 
				
			||||||
    CACHE[file].css = scss
 | 
					    CACHE[file].css = scss
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 修正那反人类的windows路径
 | 
					    if (options.LEGACY_MODE) {
 | 
				
			||||||
    output += `
 | 
					      output += `
 | 
				
			||||||
let stylesheet = new CSSStyleSheet()
 | 
					{
 | 
				
			||||||
stylesheet.path = '${filename}'
 | 
					  let stylesheet = document.createElement('style')
 | 
				
			||||||
stylesheet.replaceSync(\`${scss}\`)
 | 
					  stylesheet.setAttribute('name', '${filename}')
 | 
				
			||||||
document.adoptedStyleSheets.push(stylesheet)
 | 
					  stylesheet.textContent = \`${scss}\`
 | 
				
			||||||
 | 
					  document.head.appendChild(stylesheet)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
`
 | 
					`
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      output += `
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  let stylesheet = new CSSStyleSheet()
 | 
				
			||||||
 | 
					  stylesheet.path = '${filename}'
 | 
				
			||||||
 | 
					  stylesheet.replaceSync(\`${scss}\`)
 | 
				
			||||||
 | 
					  __sheets__.push(stylesheet)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					document.adoptedStyleSheets = __sheets__
 | 
				
			||||||
 | 
					`
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (scoped) {
 | 
					  if (scoped) {
 | 
				
			||||||
    output += `__sfc__.__scopeId = '${scopeId}'\n`
 | 
					    output += `__sfc__.__scopeId = '${scopeId}'\n`
 | 
				
			||||||
| 
						 | 
					@ -293,19 +446,15 @@ document.adoptedStyleSheets.push(stylesheet)
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 解析模板html
 | 
					 * 解析模板html
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export function parseHtml(html, { page, imports, entry }, isBuild = false) {
 | 
					export function parseHtml(html, { page, imports, entry, LEGACY_MODE }) {
 | 
				
			||||||
  return html
 | 
					  return html
 | 
				
			||||||
    .replace(/\r\n/g, '\n')
 | 
					    .replace(/\r\n/g, '\n')
 | 
				
			||||||
    .replace(
 | 
					    .replace(
 | 
				
			||||||
      '</head>',
 | 
					      '</head>',
 | 
				
			||||||
      `  <script>window.process = {env: {NODE_ENV: '${
 | 
					      `${
 | 
				
			||||||
        isBuild ? 'production' : 'development'
 | 
					        process.env.NODE_ENV === 'development'
 | 
				
			||||||
      }'}}</script>\n${
 | 
					          ? `  <script>${minify(createHmrScript(LEGACY_MODE))}</script>\n`
 | 
				
			||||||
        isBuild
 | 
					          : `  <script>${minify(LEGACY_POLYFILL)}</script>\n`
 | 
				
			||||||
          ? ''
 | 
					 | 
				
			||||||
          : `  <script>${
 | 
					 | 
				
			||||||
              Es.transformSync(HMR_SCRIPT, { minify: true }).code
 | 
					 | 
				
			||||||
            }</script>`
 | 
					 | 
				
			||||||
      }</head>`
 | 
					      }</head>`
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    .replace('{{title}}', page.title || '')
 | 
					    .replace('{{title}}', page.title || '')
 | 
				
			||||||
| 
						 | 
					@ -316,4 +465,9 @@ export function parseHtml(html, { page, imports, entry }, isBuild = false) {
 | 
				
			||||||
      '<script src="main.js"></script>',
 | 
					      '<script src="main.js"></script>',
 | 
				
			||||||
      `<script type="module">\n${entry}\n</script>`
 | 
					      `<script type="module">\n${entry}\n</script>`
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					    .replace(/\{\{#if(.*?)\}\}([\w\W]*?)\{\{#\/if\}\}/g, (m, c, code) => {
 | 
				
			||||||
 | 
					      let res = Function('return ' + c)()
 | 
				
			||||||
 | 
					      return res ? code : ''
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .replace(/process\.env\.NODE_ENV/g, `'${process.env.NODE_ENV}'`)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,113 @@
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * {}
 | 
				
			||||||
 | 
					 * @author yutent<yutent.io@gmail.com>
 | 
				
			||||||
 | 
					 * @date 2023/06/14 18:36:15
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { join, dirname, parse, normalize } from 'node:path'
 | 
				
			||||||
 | 
					import fs from 'iofs'
 | 
				
			||||||
 | 
					import Es from 'esbuild'
 | 
				
			||||||
 | 
					import { compileScss, parseJs, compileVue, parseHtml } from './compile-vue.js'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const template = fs.cat(join(process.cwd(), 'index.html')).toString()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function compileFiles(
 | 
				
			||||||
 | 
					  currentPage,
 | 
				
			||||||
 | 
					  page,
 | 
				
			||||||
 | 
					  files,
 | 
				
			||||||
 | 
					  options,
 | 
				
			||||||
 | 
					  { verbose, imports, dist } = {}
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					  let pageDir = options.IS_MPA && currentPage ? `pages/${currentPage}` : ''
 | 
				
			||||||
 | 
					  options.currentPage = currentPage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for (let [path, it] of files) {
 | 
				
			||||||
 | 
					    // 入口文件, 特殊处理
 | 
				
			||||||
 | 
					    if (page && path === page.entry) {
 | 
				
			||||||
 | 
					      let entry = fs.cat(page.entry).toString()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      entry = parseJs(entry, imports, { ...options, IS_ENTRY: true })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      let code = parseHtml(template, {
 | 
				
			||||||
 | 
					        page,
 | 
				
			||||||
 | 
					        imports,
 | 
				
			||||||
 | 
					        entry,
 | 
				
			||||||
 | 
					        LEGACY_MODE: options.LEGACY_MODE
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      fs.echo(code, join(dist, `${currentPage}.html`))
 | 
				
			||||||
 | 
					      continue
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    verbose && console.log('  解析 %s ...', it.name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    switch (it.ext) {
 | 
				
			||||||
 | 
					      case '.vue':
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          let code = await compileVue(path, 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.replace(/\.vue$/, '.js'))
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      case '.js':
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          let code = fs.cat(path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          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))
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        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':
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          let target = join(
 | 
				
			||||||
 | 
					            dist,
 | 
				
			||||||
 | 
					            'assets/',
 | 
				
			||||||
 | 
					            pageDir,
 | 
				
			||||||
 | 
					            it.name.replace(/\.scss$/, '.css')
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					          if (it.ext === '.css') {
 | 
				
			||||||
 | 
					            fs.cp(path, target)
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            let code = compileScss(path)
 | 
				
			||||||
 | 
					            for (let fn of options.plugin) {
 | 
				
			||||||
 | 
					              code = await fn('css', code)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            fs.echo(code, target)
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      default:
 | 
				
			||||||
 | 
					        fs.cp(path, join(dist, 'assets/', pageDir, it.name))
 | 
				
			||||||
 | 
					        break
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -4,57 +4,47 @@
 | 
				
			||||||
 * @date 2022/09/06 11:54:56
 | 
					 * @date 2022/09/06 11:54:56
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const JS_EXP = /<script[^>]*?>([\w\W]*?)<\/script>/
 | 
					export const JS_EXP = /(?<=\n|^)<script[^>]*?>([\w\W]*?)<\/script>/
 | 
				
			||||||
export const STYLE_EXP = /<style([^>]*?)>([\w\W]*?)<\/style>/g
 | 
					export const STYLE_EXP = /(?<=\n|^)<style([^>]*?)>([\w\W]*?)<\/style>/g
 | 
				
			||||||
export const HTML_EXP = /<template[^>]*?>([\w\W]*?)\n<\/template>/
 | 
					export const HTML_EXP = /(?<=\n|^)<template[^>]*?>([\w\W]*?)\n<\/template>/
 | 
				
			||||||
export const V_DEEP = /:deep\(([^)]*?)\)/
 | 
					export const V_DEEP = /:deep\(([^)]*?)\)(.*)/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const CSS_SHEET_EXP = /([@\w\.,#\-:>\+\~\|\(\)\[\]"'\=\s]+)\{/g
 | 
					export const CSS_SHEET_EXP = /([%@\w\.,#\-:>\+\~\|\(\)\[\]"'\=\s]+)\{/g
 | 
				
			||||||
 | 
					export const PERCENT_EXP = /^\d+%$/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const COMMON_HEADERS = {
 | 
					export const COMMON_HEADERS = {
 | 
				
			||||||
  'Cache-Control': 'no-store'
 | 
					  'Cache-Control': 'no-store'
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const HMR_SCRIPT = `
 | 
					export const SHEETS_DEF =
 | 
				
			||||||
!(function vue_live_hmr(){
 | 
					  'const __sheets__ = [...document.adoptedStyleSheets];\n'
 | 
				
			||||||
  var ws = new WebSocket(\`ws\${location.protocol === 'https:' ? 's' : ''}://\${location.host}/ws-vue-live\`)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ws.addEventListener('open', function (r) {
 | 
					export const LEGACY_POLYFILL = `!(function(){
 | 
				
			||||||
    if(vue_live_hmr.closed){
 | 
					  function join(p1, p2) {
 | 
				
			||||||
      delete vue_live_hmr.closed
 | 
					    let tmp1 = p1.split('/')
 | 
				
			||||||
      location.reload()
 | 
					    let tmp2 = p2.split('/')
 | 
				
			||||||
 | 
					    if (tmp1.at(-1) === '') {
 | 
				
			||||||
 | 
					      tmp1.pop()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    console.log('vue-live hmr ready...')
 | 
					    while (tmp2.length) {
 | 
				
			||||||
  })
 | 
					      let tmp = tmp2.shift()
 | 
				
			||||||
 | 
					      if (tmp === '.' || tmp === '') {
 | 
				
			||||||
  ws.addEventListener('close', function(){
 | 
					        continue
 | 
				
			||||||
    vue_live_hmr.closed = true
 | 
					      } else if (tmp === '..') {
 | 
				
			||||||
    setTimeout(vue_live_hmr, 2000)
 | 
					        tmp1.pop()
 | 
				
			||||||
  })
 | 
					      } else {
 | 
				
			||||||
  
 | 
					        tmp1.push(tmp)
 | 
				
			||||||
  ws.addEventListener('message', function (ev) {
 | 
					      }
 | 
				
			||||||
    var { action, data } = JSON.parse(ev.data)
 | 
					 | 
				
			||||||
  
 | 
					 | 
				
			||||||
    switch (action) {
 | 
					 | 
				
			||||||
      case 'reload':
 | 
					 | 
				
			||||||
        location.reload()
 | 
					 | 
				
			||||||
        break
 | 
					 | 
				
			||||||
  
 | 
					 | 
				
			||||||
      case 'render':
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          let tmp = [...document.adoptedStyleSheets]
 | 
					 | 
				
			||||||
          for (let i = -1, it; (it = tmp[++i]); ) {
 | 
					 | 
				
			||||||
            if (it.path === data.path) {
 | 
					 | 
				
			||||||
              let stylesheet = new CSSStyleSheet()
 | 
					 | 
				
			||||||
              stylesheet.path = data.path
 | 
					 | 
				
			||||||
              stylesheet.replaceSync(data.content)
 | 
					 | 
				
			||||||
              document.adoptedStyleSheets[i] = stylesheet
 | 
					 | 
				
			||||||
              break
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        break
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  })
 | 
					    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())
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					})()`
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										325
									
								
								lib/dev.js
								
								
								
								
							
							
						
						
									
										325
									
								
								lib/dev.js
								
								
								
								
							| 
						 | 
					@ -1,13 +1,13 @@
 | 
				
			||||||
import http from 'http'
 | 
					import http from 'node:http'
 | 
				
			||||||
import https from 'https'
 | 
					import https from 'node:http2'
 | 
				
			||||||
import fs from 'iofs'
 | 
					import fs from 'iofs'
 | 
				
			||||||
import { join, resolve, dirname } from 'path'
 | 
					import { join, dirname } from 'node:path'
 | 
				
			||||||
import { parse } from 'url'
 | 
					import { parse } from 'node:url'
 | 
				
			||||||
import socket from './ws.js'
 | 
					import socket from './ws.js'
 | 
				
			||||||
import chokidar from 'chokidar'
 | 
					import chokidar from 'chokidar'
 | 
				
			||||||
import { red, cyan, blue } from 'kolorist'
 | 
					import { red } from 'kolorist'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { friendlyErrors } from './utils.js'
 | 
					import { friendlyErrors, defaultCustomElement, gzip } from './utils.js'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { compileScss, parseJs, compileVue, parseHtml } from './compile-vue.js'
 | 
					import { compileScss, parseJs, compileVue, parseHtml } from './compile-vue.js'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,9 +15,13 @@ import MIME_TYPES from './mime-tpyes.js'
 | 
				
			||||||
import { COMMON_HEADERS } from './constants.js'
 | 
					import { COMMON_HEADERS } from './constants.js'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const noc = Buffer.from('')
 | 
					const noc = Buffer.from('')
 | 
				
			||||||
const SERVER_OPTIONS = {}
 | 
					const SERVER_OPTIONS = { allowHTTP1: true }
 | 
				
			||||||
const CACHE = {} //文件缓存, 用于hmr
 | 
					const CACHE = {} //文件缓存, 用于hmr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function readFile(file) {
 | 
				
			||||||
 | 
					  return (file && fs.cat(file)?.toString()) || ''
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default async function createServer(root = '', conf = {}) {
 | 
					export default async function createServer(root = '', conf = {}) {
 | 
				
			||||||
  const SOURCE_DIR = join(root, 'src')
 | 
					  const SOURCE_DIR = join(root, 'src')
 | 
				
			||||||
  const PUBLIC_DIR = join(root, 'public')
 | 
					  const PUBLIC_DIR = join(root, 'public')
 | 
				
			||||||
| 
						 | 
					@ -26,6 +30,22 @@ export default async function createServer(root = '', conf = {}) {
 | 
				
			||||||
  const PORT = conf.devServer.port || 8080
 | 
					  const PORT = conf.devServer.port || 8080
 | 
				
			||||||
  const USE_HTTPS = conf.devServer.https
 | 
					  const USE_HTTPS = conf.devServer.https
 | 
				
			||||||
  const DOMAIN = conf.devServer.domain || 'localhost'
 | 
					  const DOMAIN = conf.devServer.domain || 'localhost'
 | 
				
			||||||
 | 
					  const INJECT_SCSS = readFile(conf.inject?.scss)
 | 
				
			||||||
 | 
					  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']
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (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)
 | 
				
			||||||
| 
						 | 
					@ -37,7 +57,7 @@ export default async function createServer(root = '', conf = {}) {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const server = (USE_HTTPS ? https : http)
 | 
					  const server = (USE_HTTPS ? https : http)
 | 
				
			||||||
    .createServer(SERVER_OPTIONS)
 | 
					    [USE_HTTPS ? 'createSecureServer' : 'createServer'](SERVER_OPTIONS)
 | 
				
			||||||
    .listen(PORT)
 | 
					    .listen(PORT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const ws = socket(server)
 | 
					  const ws = socket(server)
 | 
				
			||||||
| 
						 | 
					@ -54,7 +74,7 @@ export default async function createServer(root = '', conf = {}) {
 | 
				
			||||||
    currentPage = ''
 | 
					    currentPage = ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  server
 | 
					  server
 | 
				
			||||||
    .on('request', function (req, res) {
 | 
					    .on('request', async 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)
 | 
				
			||||||
| 
						 | 
					@ -67,7 +87,7 @@ export default async function createServer(root = '', conf = {}) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (prefix && req.url === '/') {
 | 
					      if (prefix && req.url === '/') {
 | 
				
			||||||
        res.setHeader('Location', DEPLOY_PATH)
 | 
					        res.setHeader('Location', DEPLOY_PATH)
 | 
				
			||||||
        res.writeHead(302, 'Redirect')
 | 
					        res.writeHead(302, USE_HTTPS ? void 0 : 'Redirect')
 | 
				
			||||||
        return res.end('')
 | 
					        return res.end('')
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -92,7 +112,7 @@ export default async function createServer(root = '', conf = {}) {
 | 
				
			||||||
            pageName = tmp.join('.')
 | 
					            pageName = tmp.join('.')
 | 
				
			||||||
            // 页面不存在时输出404, 避免进程崩溃退出
 | 
					            // 页面不存在时输出404, 避免进程崩溃退出
 | 
				
			||||||
            if (!conf.pages[pageName]) {
 | 
					            if (!conf.pages[pageName]) {
 | 
				
			||||||
              res.writeHead(404, 'Not Found')
 | 
					              res.writeHead(404, USE_HTTPS ? void 0 : 'Not Found')
 | 
				
			||||||
              return res.end(`Oops!!! 404 Not Found`)
 | 
					              return res.end(`Oops!!! 404 Not Found`)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -130,8 +150,13 @@ export default async function createServer(root = '', conf = {}) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (isIndex) {
 | 
					      if (isIndex) {
 | 
				
			||||||
        res.setHeader('content-type', MIME_TYPES.html)
 | 
					        res.setHeader('content-type', MIME_TYPES.html)
 | 
				
			||||||
        res.writeHead(200, 'OK')
 | 
					        res.writeHead(200, USE_HTTPS ? void 0 : 'OK')
 | 
				
			||||||
        res.end('<ul>' + indexPage + '</ul>')
 | 
					        res.end(
 | 
				
			||||||
 | 
					          '<style>body{font:14px/1.5 Arial}ol{display:flex;flex-wrap:wrap;}li{width:30%;}a{color:teal}a:visited{color:orange;}</style>' +
 | 
				
			||||||
 | 
					            '<div>注意: 你看到这个页面, 仅在开发时可见。<br>仅为了方便开发多页应用时访问自己想要修改的页面, 而不需要手动输入地址。</div><ol>' +
 | 
				
			||||||
 | 
					            indexPage +
 | 
				
			||||||
 | 
					            '</ol>'
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        res.setHeader('accept-ranges', 'bytes')
 | 
					        res.setHeader('accept-ranges', 'bytes')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -146,25 +171,44 @@ 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, conf.imports, {
 | 
					              entry = parseJs(
 | 
				
			||||||
                IS_MPA,
 | 
					                entry,
 | 
				
			||||||
                currentPage,
 | 
					                conf.imports,
 | 
				
			||||||
                IS_ENTRY: true,
 | 
					                {
 | 
				
			||||||
                DEPLOY_PATH
 | 
					                  IS_MPA,
 | 
				
			||||||
              })
 | 
					                  currentPage,
 | 
				
			||||||
 | 
					                  IS_ENTRY: true,
 | 
				
			||||||
 | 
					                  DEPLOY_PATH,
 | 
				
			||||||
 | 
					                  LEGACY_MODE,
 | 
				
			||||||
 | 
					                  isCustomElement,
 | 
				
			||||||
 | 
					                  plugin,
 | 
				
			||||||
 | 
					                  define
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                page.entry
 | 
				
			||||||
 | 
					              )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              code = parseHtml(html, { page, imports: conf.imports, entry })
 | 
					              for (let fn of plugin) {
 | 
				
			||||||
 | 
					                entry = await fn('js', entry)
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              code = parseHtml(html, {
 | 
				
			||||||
 | 
					                page,
 | 
				
			||||||
 | 
					                imports: conf.imports,
 | 
				
			||||||
 | 
					                entry,
 | 
				
			||||||
 | 
					                LEGACY_MODE
 | 
				
			||||||
 | 
					              })
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            break
 | 
					            break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          case 'vue':
 | 
					          case 'vue':
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
              let rpath = pathname.replace(/@\//, '')
 | 
					              let rpath = pathname.replace('@/', '')
 | 
				
			||||||
              let file
 | 
					              let file
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              if (IS_MPA) {
 | 
					              if (IS_MPA) {
 | 
				
			||||||
                if (rpath.startsWith(currentPage)) {
 | 
					                // 判断前后2个值相等, 避免出现目录名和页面名字相同时走错逻辑
 | 
				
			||||||
 | 
					                if (rpath === pathname && rpath.startsWith(currentPage)) {
 | 
				
			||||||
                  file = join(pagesDir, rpath.slice(currentPage.length))
 | 
					                  file = join(pagesDir, rpath.slice(currentPage.length))
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                  file = join(SOURCE_DIR, rpath)
 | 
					                  file = join(SOURCE_DIR, rpath)
 | 
				
			||||||
| 
						 | 
					@ -174,19 +218,28 @@ export default async function createServer(root = '', conf = {}) {
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
              if (!fs.isfile(file)) {
 | 
					              if (!fs.isfile(file)) {
 | 
				
			||||||
                friendlyErrors(pathname, ext)
 | 
					                friendlyErrors(pathname, ext)
 | 
				
			||||||
                res.writeHead(404, 'Not Found')
 | 
					                res.writeHead(404, USE_HTTPS ? void 0 : 'Not Found')
 | 
				
			||||||
                res.end('')
 | 
					                res.end('')
 | 
				
			||||||
                return
 | 
					                return
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              code = compileVue(file, conf.imports, {
 | 
					              code = await compileVue(file, conf.imports, {
 | 
				
			||||||
                IS_MPA,
 | 
					                IS_MPA,
 | 
				
			||||||
                currentPage,
 | 
					                currentPage,
 | 
				
			||||||
                SOURCE_DIR,
 | 
					                SOURCE_DIR,
 | 
				
			||||||
                CACHE,
 | 
					                CACHE,
 | 
				
			||||||
                DEPLOY_PATH
 | 
					                DEPLOY_PATH,
 | 
				
			||||||
 | 
					                INJECT_SCSS,
 | 
				
			||||||
 | 
					                LEGACY_MODE,
 | 
				
			||||||
 | 
					                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
 | 
				
			||||||
| 
						 | 
					@ -194,30 +247,47 @@ export default async function createServer(root = '', conf = {}) {
 | 
				
			||||||
          case 'scss':
 | 
					          case 'scss':
 | 
				
			||||||
          case 'css':
 | 
					          case 'css':
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
              let file = join(SOURCE_DIR, pathname.replace(/@\//, ''))
 | 
					              let file = join(SOURCE_DIR, pathname.replace('@/', ''))
 | 
				
			||||||
              if (!fs.isfile(file)) {
 | 
					              if (!fs.isfile(file)) {
 | 
				
			||||||
                file = join(PUBLIC_DIR, pathname.replace(/@\//, ''))
 | 
					                file = join(PUBLIC_DIR, pathname.replace('@/', ''))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (!fs.isfile(file)) {
 | 
					                if (!fs.isfile(file)) {
 | 
				
			||||||
                  friendlyErrors(pathname, ext)
 | 
					                  friendlyErrors(pathname, ext)
 | 
				
			||||||
                  res.setHeader('content-type', MIME_TYPES.html)
 | 
					                  res.writeHead(404, USE_HTTPS ? void 0 : 'Not Found')
 | 
				
			||||||
                  res.writeHead(404, 'Not Found')
 | 
					 | 
				
			||||||
                  res.end('')
 | 
					                  res.end('')
 | 
				
			||||||
                  return
 | 
					                  return
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
 | 
					              if (file === conf.inject?.scss) {
 | 
				
			||||||
 | 
					                console.log(red('设置为注入的样式文件不可被vue/js文件引用\n'))
 | 
				
			||||||
 | 
					                res.writeHead(404, USE_HTTPS ? void 0 : 'Not Found')
 | 
				
			||||||
 | 
					                res.end('')
 | 
				
			||||||
 | 
					                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) {
 | 
				
			||||||
                if (rpath.startsWith(currentPage)) {
 | 
					                // 判断前后2个值相等, 避免出现目录名和页面名字相同时走错逻辑
 | 
				
			||||||
 | 
					                if (rpath === pathname && rpath.startsWith(currentPage)) {
 | 
				
			||||||
                  file = join(pagesDir, rpath.slice(currentPage.length))
 | 
					                  file = join(pagesDir, rpath.slice(currentPage.length))
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                  file = join(SOURCE_DIR, rpath)
 | 
					                  file = join(SOURCE_DIR, rpath)
 | 
				
			||||||
| 
						 | 
					@ -229,45 +299,92 @@ 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))) {
 | 
				
			||||||
                code = fs.cat(join(PUBLIC_DIR, rpath))
 | 
					                file = join(PUBLIC_DIR, rpath)
 | 
				
			||||||
 | 
					                code = fs.cat(file)
 | 
				
			||||||
              } else {
 | 
					              } else {
 | 
				
			||||||
                friendlyErrors(rpath, ext)
 | 
					                friendlyErrors(rpath, ext)
 | 
				
			||||||
                res.writeHead(404, 'Not Found')
 | 
					                res.writeHead(404, USE_HTTPS ? void 0 : 'Not Found')
 | 
				
			||||||
                res.end('')
 | 
					                res.end('')
 | 
				
			||||||
                return
 | 
					                return
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
              code = parseJs(code + '', conf.imports, {
 | 
					              if (isJson) {
 | 
				
			||||||
                IS_MPA,
 | 
					                try {
 | 
				
			||||||
                currentPage,
 | 
					                  code =
 | 
				
			||||||
                DEPLOY_PATH
 | 
					                    'export default ' + JSON.stringify(JSON.parse(code + ''))
 | 
				
			||||||
              })
 | 
					                } catch (err) {
 | 
				
			||||||
              res.setHeader('content-type', MIME_TYPES.js)
 | 
					                  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])
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            break
 | 
					            break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          default:
 | 
					          default:
 | 
				
			||||||
            res.setHeader('content-type', MIME_TYPES[ext] || MIME_TYPES.other)
 | 
					            res.setHeader('content-type', MIME_TYPES[ext] || MIME_TYPES.other)
 | 
				
			||||||
 | 
					            let pub_file = join(PUBLIC_DIR, pathname)
 | 
				
			||||||
 | 
					            let source_file = join(SOURCE_DIR, pathname)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (fs.isfile(join(PUBLIC_DIR, pathname))) {
 | 
					            if (fs.isfile(pub_file)) {
 | 
				
			||||||
              code = fs.cat(join(PUBLIC_DIR, pathname))
 | 
					              code = fs.cat(pub_file)
 | 
				
			||||||
            } else if (fs.isfile(join(SOURCE_DIR, pathname))) {
 | 
					              if (code) {
 | 
				
			||||||
              code = fs.cat(join(SOURCE_DIR, pathname))
 | 
					                let stat = fs.stat(pub_file)
 | 
				
			||||||
 | 
					                res.setHeader(
 | 
				
			||||||
 | 
					                  'Last-Modified',
 | 
				
			||||||
 | 
					                  new Date(stat.mtime).toGMTString()
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            } else if (fs.isfile(source_file)) {
 | 
				
			||||||
 | 
					              code = fs.cat(source_file)
 | 
				
			||||||
 | 
					              if (code) {
 | 
				
			||||||
 | 
					                let stat = fs.stat(source_file)
 | 
				
			||||||
 | 
					                res.setHeader(
 | 
				
			||||||
 | 
					                  'Last-Modified',
 | 
				
			||||||
 | 
					                  new Date(stat.mtime).toGMTString()
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
              code = null
 | 
					              code = null
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if (code === null) {
 | 
					            if (code === null) {
 | 
				
			||||||
              friendlyErrors(pathname, ext)
 | 
					              friendlyErrors(pathname, ext)
 | 
				
			||||||
              res.writeHead(404, 'Not Found')
 | 
					              res.writeHead(404, USE_HTTPS ? void 0 : 'Not Found')
 | 
				
			||||||
              res.end('')
 | 
					              res.end('')
 | 
				
			||||||
              return
 | 
					              return
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            break
 | 
					            break
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        res.setHeader('content-length', Buffer.byteLength(code || noc))
 | 
					        if (ENABLE_GZIP) {
 | 
				
			||||||
        res.writeHead(200, 'OK')
 | 
					          code = gzip(code || noc)
 | 
				
			||||||
        res.end(code || noc)
 | 
					          res.setHeader('Content-Encoding', 'gzip')
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          code = code || noc
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        res.setHeader('Content-Length', Buffer.byteLength(code))
 | 
				
			||||||
 | 
					        res.writeHead(200, USE_HTTPS ? void 0 : 'OK')
 | 
				
			||||||
 | 
					        res.end(code)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -284,7 +401,7 @@ export default async function createServer(root = '', conf = {}) {
 | 
				
			||||||
      console.log(
 | 
					      console.log(
 | 
				
			||||||
        '  本地: %s://%s:%d%s',
 | 
					        '  本地: %s://%s:%d%s',
 | 
				
			||||||
        USE_HTTPS ? 'https' : 'http',
 | 
					        USE_HTTPS ? 'https' : 'http',
 | 
				
			||||||
        '127.0.0.1',
 | 
					        USE_HTTPS ? 'localhost' : '127.0.0.1',
 | 
				
			||||||
        PORT,
 | 
					        PORT,
 | 
				
			||||||
        DEPLOY_PATH
 | 
					        DEPLOY_PATH
 | 
				
			||||||
      )
 | 
					      )
 | 
				
			||||||
| 
						 | 
					@ -295,60 +412,78 @@ export default async function createServer(root = '', conf = {}) {
 | 
				
			||||||
        PORT,
 | 
					        PORT,
 | 
				
			||||||
        DEPLOY_PATH
 | 
					        DEPLOY_PATH
 | 
				
			||||||
      )
 | 
					      )
 | 
				
			||||||
    })
 | 
					      chokidar
 | 
				
			||||||
 | 
					        .watch([SOURCE_DIR, PUBLIC_DIR, join(root, './index.html')])
 | 
				
			||||||
 | 
					        .on('all', async (act, filePath) => {
 | 
				
			||||||
 | 
					          if (ready) {
 | 
				
			||||||
 | 
					            let file = filePath.slice(SOURCE_DIR.length)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  chokidar
 | 
					            if (act === 'add' || act === 'change') {
 | 
				
			||||||
    .watch([SOURCE_DIR, PUBLIC_DIR, join(root, './index.html')])
 | 
					              let ext = file.slice(file.lastIndexOf('.') + 1)
 | 
				
			||||||
    .on('all', (act, filePath) => {
 | 
					 | 
				
			||||||
      if (ready) {
 | 
					 | 
				
			||||||
        let file = filePath.slice(SOURCE_DIR.length)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (act === 'add' || act === 'change') {
 | 
					              switch (ext) {
 | 
				
			||||||
          let ext = file.slice(file.lastIndexOf('.') + 1)
 | 
					                case 'css':
 | 
				
			||||||
 | 
					                case 'scss':
 | 
				
			||||||
 | 
					                  {
 | 
				
			||||||
 | 
					                    let content = ''
 | 
				
			||||||
 | 
					                    if (filePath === conf.inject?.scss) {
 | 
				
			||||||
 | 
					                      return
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    if (ext === 'scss') {
 | 
				
			||||||
 | 
					                      content = compileScss(filePath)
 | 
				
			||||||
 | 
					                    } 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 }
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					                  break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          switch (ext) {
 | 
					                case 'vue':
 | 
				
			||||||
            case 'css':
 | 
					                  {
 | 
				
			||||||
            case 'scss':
 | 
					                    let content = await compileVue(filePath, conf.imports, {
 | 
				
			||||||
              {
 | 
					                      IS_MPA,
 | 
				
			||||||
                let content = fs.cat(filePath).toString()
 | 
					                      currentPage,
 | 
				
			||||||
                ws.send({
 | 
					                      SOURCE_DIR,
 | 
				
			||||||
                  action: 'render',
 | 
					                      CACHE,
 | 
				
			||||||
                  data: { path: file.replace(/\\/g, '/'), content }
 | 
					                      DEPLOY_PATH,
 | 
				
			||||||
                })
 | 
					                      INJECT_SCSS,
 | 
				
			||||||
              }
 | 
					                      LEGACY_MODE,
 | 
				
			||||||
              break
 | 
					                      isCustomElement,
 | 
				
			||||||
 | 
					                      plugin,
 | 
				
			||||||
 | 
					                      define
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                    let tmp = CACHE[filePath]
 | 
				
			||||||
 | 
					                    if (tmp.changed) {
 | 
				
			||||||
 | 
					                      ws.send({ action: 'reload' })
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                      ws.send({
 | 
				
			||||||
 | 
					                        action: 'render',
 | 
				
			||||||
 | 
					                        data: {
 | 
				
			||||||
 | 
					                          path: file.replace(/\\/g, '/'),
 | 
				
			||||||
 | 
					                          content: tmp.css
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                      })
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					                  break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            case 'vue':
 | 
					                default:
 | 
				
			||||||
              {
 | 
					 | 
				
			||||||
                let content = compileVue(filePath, conf.imports, {
 | 
					 | 
				
			||||||
                  IS_MPA,
 | 
					 | 
				
			||||||
                  currentPage,
 | 
					 | 
				
			||||||
                  SOURCE_DIR,
 | 
					 | 
				
			||||||
                  CACHE,
 | 
					 | 
				
			||||||
                  DEPLOY_PATH
 | 
					 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
                let tmp = CACHE[filePath]
 | 
					 | 
				
			||||||
                if (tmp.changed) {
 | 
					 | 
				
			||||||
                  ws.send({ action: 'reload' })
 | 
					                  ws.send({ action: 'reload' })
 | 
				
			||||||
                } else {
 | 
					                  break
 | 
				
			||||||
                  ws.send({
 | 
					 | 
				
			||||||
                    action: 'render',
 | 
					 | 
				
			||||||
                    data: { path: file.replace(/\\/g, '/'), content: tmp.css }
 | 
					 | 
				
			||||||
                  })
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
              break
 | 
					            } else if (act === 'unlink' || act === 'unlinkDir') {
 | 
				
			||||||
 | 
					 | 
				
			||||||
            default:
 | 
					 | 
				
			||||||
              ws.send({ action: 'reload' })
 | 
					              ws.send({ action: 'reload' })
 | 
				
			||||||
              break
 | 
					            }
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        } else if (act === 'unlink' || act === 'unlinkDir') {
 | 
					        })
 | 
				
			||||||
          ws.send({ action: 'reload' })
 | 
					        .on('ready', () => {
 | 
				
			||||||
        }
 | 
					          ready = true
 | 
				
			||||||
      }
 | 
					        })
 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    .on('ready', () => {
 | 
					 | 
				
			||||||
      ready = true
 | 
					 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										328
									
								
								lib/prod.js
								
								
								
								
							
							
						
						
									
										328
									
								
								lib/prod.js
								
								
								
								
							| 
						 | 
					@ -1,164 +1,224 @@
 | 
				
			||||||
 | 
					import { join, dirname, parse, normalize } from 'node:path'
 | 
				
			||||||
 | 
					import { Worker, parentPort } from 'node:worker_threads'
 | 
				
			||||||
 | 
					import os from 'node:os'
 | 
				
			||||||
import fs from 'iofs'
 | 
					import fs from 'iofs'
 | 
				
			||||||
import { join, resolve, dirname, parse } from 'path'
 | 
					 | 
				
			||||||
import Es from 'esbuild'
 | 
					 | 
				
			||||||
import { compileScss, parseJs, compileVue, parseHtml } from './compile-vue.js'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const noc = Buffer.from('')
 | 
					import { compileFiles } from './compile.js'
 | 
				
			||||||
 | 
					import { defaultCustomElement } from './utils.js'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function compile(root = '', dist = '', conf = {}) {
 | 
					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 __filename = normalize(import.meta.url.slice(IS_WIN ? 8 : 7))
 | 
				
			||||||
 | 
					const __dirname = dirname(__filename)
 | 
				
			||||||
 | 
					const WORKER_POOL = new Set() // 线程池
 | 
				
			||||||
 | 
					const JOBS_QUEUE = [] // 任务队列
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function readFile(file) {
 | 
				
			||||||
 | 
					  return (file && fs.cat(file)?.toString()) || ''
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function doJob() {
 | 
				
			||||||
 | 
					  while (JOBS_QUEUE.length && WORKER_POOL.size) {
 | 
				
			||||||
 | 
					    let job = JOBS_QUEUE.shift()
 | 
				
			||||||
 | 
					    let worker = WORKER_POOL.values().next().value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    WORKER_POOL.delete(worker)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    worker.once('message', _ => {
 | 
				
			||||||
 | 
					      if (JOBS_QUEUE.length) {
 | 
				
			||||||
 | 
					        WORKER_POOL.add(worker)
 | 
				
			||||||
 | 
					        doJob()
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        worker.terminate()
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    worker.postMessage(job)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function compile(root = '', dist = '', conf = {}, verbose) {
 | 
				
			||||||
  //
 | 
					  //
 | 
				
			||||||
  const SOURCE_DIR = join(root, 'src')
 | 
					  const SOURCE_DIR = join(root, 'src')
 | 
				
			||||||
  const PUBLIC_DIR = join(root, 'public')
 | 
					  const PUBLIC_DIR = join(root, 'public')
 | 
				
			||||||
  const DEPLOY_PATH = conf.base || '' // 部署目录, 默认是根目录部署
 | 
					  const DEPLOY_PATH = conf.base || '' // 部署目录, 默认是根目录部署
 | 
				
			||||||
  const IS_MPA = Object.keys(conf.pages).length > 1
 | 
					  const PAGES_KEYS = Object.keys(conf.pages)
 | 
				
			||||||
 | 
					  const IS_MPA = PAGES_KEYS.length > 1
 | 
				
			||||||
 | 
					  const PAGES_PREFIX = PAGES_KEYS.map(it =>
 | 
				
			||||||
 | 
					    IS_WIN ? `${PREFIX + it}\\` : `${PREFIX + it}/`
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					  const INJECT_SCSS = readFile(conf.inject?.scss)
 | 
				
			||||||
 | 
					  const LEGACY_MODE = !!conf.legacy
 | 
				
			||||||
 | 
					  const {
 | 
				
			||||||
 | 
					    ABS_CONFIG_FILEPATH,
 | 
				
			||||||
 | 
					    compileOptions = {},
 | 
				
			||||||
 | 
					    define = {},
 | 
				
			||||||
 | 
					    plugin = []
 | 
				
			||||||
 | 
					  } = conf
 | 
				
			||||||
 | 
					  const { isCustomElement = defaultCustomElement } = compileOptions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  conf.inject = conf.inject || { scss: '' }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let timeStart = Date.now()
 | 
					  let timeStart = Date.now()
 | 
				
			||||||
  let template = fs.cat(join(process.env.PWD, 'index.html')).toString()
 | 
					  let template = fs.cat(join(process.cwd(), 'index.html')).toString()
 | 
				
			||||||
 | 
					  let list = new Map()
 | 
				
			||||||
  let list = fs
 | 
					  let options = {
 | 
				
			||||||
    .ls(SOURCE_DIR, true)
 | 
					    IS_MPA,
 | 
				
			||||||
    .map(it => ({
 | 
					    SOURCE_DIR,
 | 
				
			||||||
      name: it.slice(SOURCE_DIR.length + 1),
 | 
					    DEPLOY_PATH,
 | 
				
			||||||
      path: it,
 | 
					    INJECT_SCSS,
 | 
				
			||||||
      ext: parse(it).ext
 | 
					    LEGACY_MODE,
 | 
				
			||||||
    }))
 | 
					    ABS_CONFIG_FILEPATH,
 | 
				
			||||||
    .filter(it => fs.isfile(it.path) && it.ext !== '')
 | 
					    define
 | 
				
			||||||
 | 
					 | 
				
			||||||
  console.log(list)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  let compileFiles = function (currentPage, page, files) {
 | 
					 | 
				
			||||||
    for (let it of files) {
 | 
					 | 
				
			||||||
      // 入口文件, 特殊处理
 | 
					 | 
				
			||||||
      if (page && it.path === page.entry) {
 | 
					 | 
				
			||||||
        let entry = fs.cat(page.entry).toString()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        entry = parseJs(
 | 
					 | 
				
			||||||
          entry,
 | 
					 | 
				
			||||||
          conf.imports,
 | 
					 | 
				
			||||||
          { IS_MPA, currentPage, IS_ENTRY: true, DEPLOY_PATH },
 | 
					 | 
				
			||||||
          true
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let code = parseHtml(
 | 
					 | 
				
			||||||
          template,
 | 
					 | 
				
			||||||
          { page, imports: conf.imports, entry },
 | 
					 | 
				
			||||||
          true
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        fs.echo(code, join(dist, `${currentPage}.html`))
 | 
					 | 
				
			||||||
        continue
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      console.log('  解析 %s ...', it.name)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      let pageDir = IS_MPA && currentPage ? `pages/${currentPage}` : ''
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      switch (it.ext) {
 | 
					 | 
				
			||||||
        case '.vue':
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            let code = compileVue(
 | 
					 | 
				
			||||||
              it.path,
 | 
					 | 
				
			||||||
              conf.imports,
 | 
					 | 
				
			||||||
              { IS_MPA, currentPage, SOURCE_DIR, DEPLOY_PATH },
 | 
					 | 
				
			||||||
              true
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            Es.transform(code, { minify: true }).then(r => {
 | 
					 | 
				
			||||||
              fs.echo(
 | 
					 | 
				
			||||||
                r.code,
 | 
					 | 
				
			||||||
                join(dist, 'assets/', pageDir, it.name.replace(/\.vue$/, '.js'))
 | 
					 | 
				
			||||||
              )
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          break
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        case '.js':
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            let code = fs.cat(it.path)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            code = parseJs(
 | 
					 | 
				
			||||||
              code + '',
 | 
					 | 
				
			||||||
              conf.imports,
 | 
					 | 
				
			||||||
              { IS_MPA, currentPage, DEPLOY_PATH },
 | 
					 | 
				
			||||||
              true
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            Es.transform(code, { minify: true }).then(r => {
 | 
					 | 
				
			||||||
              fs.echo(r.code, join(dist, 'assets/', pageDir, it.name))
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          break
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        case '.scss':
 | 
					 | 
				
			||||||
        case '.css':
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            let code = compileScss(it.path)
 | 
					 | 
				
			||||||
            if (!it.name.startsWith('assets')) {
 | 
					 | 
				
			||||||
              it.name = 'assets/' + it.name
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            fs.echo(code, join(dist, it.name.replace(/\.scss$/, '.css')))
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          break
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        default:
 | 
					 | 
				
			||||||
          fs.cp(it.path, join(dist, it.name))
 | 
					 | 
				
			||||||
          break
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  for (let currentPage in conf.pages) {
 | 
					  fs.ls(SOURCE_DIR, true).forEach(path => {
 | 
				
			||||||
    let page = conf.pages[currentPage]
 | 
					    if (fs.isdir(path)) {
 | 
				
			||||||
    let dir = dirname(page.entry)
 | 
					      return
 | 
				
			||||||
    let files = list
 | 
					    }
 | 
				
			||||||
    if (IS_MPA) {
 | 
					
 | 
				
			||||||
      files = []
 | 
					    let name = path.slice(SOURCE_DIR.length + 1)
 | 
				
			||||||
      fs.ls(dir, true).forEach(it => {
 | 
					    let it = {
 | 
				
			||||||
        if (fs.isdir(it)) {
 | 
					      name,
 | 
				
			||||||
 | 
					      ext: parse(name).ext
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (it.ext) {
 | 
				
			||||||
 | 
					      if (IS_MPA && it.name.startsWith(PREFIX)) {
 | 
				
			||||||
 | 
					        if (PAGES_PREFIX.some(it => it.startsWith(it.name))) {
 | 
				
			||||||
 | 
					          list.set(path, it)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (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正在处理静态资源 ...')
 | 
				
			||||||
 | 
					    fs.ls(PUBLIC_DIR, true).forEach(it => {
 | 
				
			||||||
 | 
					      let ext = parse(it).ext
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (ext && fs.isfile(it)) {
 | 
				
			||||||
 | 
					        let name = it.slice(PUBLIC_DIR.length + 1)
 | 
				
			||||||
 | 
					        verbose && console.log('  复制 %s ...', name)
 | 
				
			||||||
 | 
					        fs.cp(it, join(dist, name))
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (IS_MPA) {
 | 
				
			||||||
 | 
					    for (let currentPage of PAGES_KEYS) {
 | 
				
			||||||
 | 
					      let page = conf.pages[currentPage]
 | 
				
			||||||
 | 
					      let dir = dirname(page.entry)
 | 
				
			||||||
 | 
					      let files = new Map()
 | 
				
			||||||
 | 
					      let chunk = new Map()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      fs.ls(dir, true).forEach(path => {
 | 
				
			||||||
 | 
					        if (fs.isdir(path)) {
 | 
				
			||||||
          return
 | 
					          return
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        let idx = list.findIndex(_ => _.path === it)
 | 
					
 | 
				
			||||||
        let name = it.slice(dir.length + 1)
 | 
					        let name = path.slice(dir.length + 1)
 | 
				
			||||||
        let ext = parse(name).ext
 | 
					        let ext = parse(name).ext
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (ext === '') {
 | 
					        if (ext === '') {
 | 
				
			||||||
          return
 | 
					          return
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        list.splice(idx, 1)
 | 
					        list.delete(path)
 | 
				
			||||||
 | 
					        files.set(path, { name, ext })
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        files.push({
 | 
					      if (THREADS_NUM > 0) {
 | 
				
			||||||
          name,
 | 
					        chunk.set(currentPage, { page, files })
 | 
				
			||||||
          path: it,
 | 
					        JOBS_QUEUE.push(chunk)
 | 
				
			||||||
          ext
 | 
					        doJob()
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        console.log(`正在生成 ${currentPage}.html ...`)
 | 
				
			||||||
 | 
					        compileFiles(currentPage, page, files, options, {
 | 
				
			||||||
 | 
					          verbose,
 | 
				
			||||||
 | 
					          dist,
 | 
				
			||||||
 | 
					          imports: conf.imports
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 公共依赖
 | 
				
			||||||
 | 
					    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
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    console.log('正在生成 %s ...', `${currentPage}.html`)
 | 
					  } else {
 | 
				
			||||||
    compileFiles(currentPage, page, files)
 | 
					    // 每个线程处理的文件数
 | 
				
			||||||
  }
 | 
					    let chunkSize = Math.ceil(list.size / THREADS_NUM)
 | 
				
			||||||
 | 
					    let currentPage = PAGES_KEYS[0]
 | 
				
			||||||
 | 
					    let page = conf.pages[currentPage]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (IS_MPA) {
 | 
					    console.log(`正在生成 ${currentPage}.html ...`)
 | 
				
			||||||
    console.log('\n正在解析公共依赖 ...')
 | 
					 | 
				
			||||||
    compileFiles('', null, list)
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  //
 | 
					    if (THREADS_NUM > 0 && list.size > THREADS_NUM * 10) {
 | 
				
			||||||
  if (fs.isdir(PUBLIC_DIR)) {
 | 
					      list = [...list]
 | 
				
			||||||
    console.log('\n正在处理静态资源 ...')
 | 
					      for (let i = 0; i < THREADS_NUM; i++) {
 | 
				
			||||||
    fs.ls(PUBLIC_DIR, true).forEach(it => {
 | 
					        let start = i * chunkSize
 | 
				
			||||||
      let ext = parse(it).ext
 | 
					        let end = start + chunkSize
 | 
				
			||||||
 | 
					        let chunk = new Map()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (ext === '') {
 | 
					        chunk.set(currentPage, { page, files: list.slice(start, end) })
 | 
				
			||||||
        return
 | 
					
 | 
				
			||||||
 | 
					        JOBS_QUEUE.push(chunk)
 | 
				
			||||||
 | 
					        doJob()
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
      if (fs.isfile(it)) {
 | 
					      options.plugin = plugin
 | 
				
			||||||
        let name = it.slice(PUBLIC_DIR.length + 1)
 | 
					      options.isCustomElement = isCustomElement
 | 
				
			||||||
        console.log('  复制 %s ...', name)
 | 
					      compileFiles(currentPage, page, list, options, {
 | 
				
			||||||
        fs.cp(it, join(dist, name))
 | 
					        verbose,
 | 
				
			||||||
      }
 | 
					        dist,
 | 
				
			||||||
    })
 | 
					        imports: conf.imports
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  console.log('\n页面处理完成, 耗时 %ss\n', (Date.now() - timeStart) / 1000)
 | 
					  process.on('exit', _ => {
 | 
				
			||||||
 | 
					    console.log('\n页面处理完成, 耗时 %ss\n', (Date.now() - timeStart) / 1000)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,40 @@
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 子线程
 | 
				
			||||||
 | 
					 * @author yutent<yutent.io@gmail.com>
 | 
				
			||||||
 | 
					 * @date 2023/06/14 16:15:39
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import { parentPort, workerData } from 'node:worker_threads'
 | 
				
			||||||
 | 
					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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { isCustomElement = defaultCustomElement } = compileOptions
 | 
				
			||||||
 | 
					options.isCustomElement = isCustomElement
 | 
				
			||||||
 | 
					options.plugin = plugin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					async function doJob(job) {
 | 
				
			||||||
 | 
					  let [currentPage, { page, files }] = job.entries().next().value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  options.IS_MPA &&
 | 
				
			||||||
 | 
					    console.log(
 | 
				
			||||||
 | 
					      currentPage
 | 
				
			||||||
 | 
					        ? `正在生成 ${currentPage}.html ...`
 | 
				
			||||||
 | 
					        : '\n正在解析公共依赖 ...'
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  await compileFiles(currentPage, page, files, options, {
 | 
				
			||||||
 | 
					    verbose,
 | 
				
			||||||
 | 
					    dist,
 | 
				
			||||||
 | 
					    imports
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  parentPort.postMessage(true)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					parentPort.on('message', doJob)
 | 
				
			||||||
							
								
								
									
										96
									
								
								lib/utils.js
								
								
								
								
							
							
						
						
									
										96
									
								
								lib/utils.js
								
								
								
								
							| 
						 | 
					@ -1,4 +1,33 @@
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * {一些工具类函数}
 | 
				
			||||||
 | 
					 * @author yutent<yutent.io@gmail.com>
 | 
				
			||||||
 | 
					 * @date 2023/05/22 14:52:00
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { createHash, randomUUID } from 'node:crypto'
 | 
				
			||||||
 | 
					import { join } from 'node:path'
 | 
				
			||||||
 | 
					import { gzipSync } from 'node:zlib'
 | 
				
			||||||
import { red, cyan, blue } from 'kolorist'
 | 
					import { red, cyan, blue } from 'kolorist'
 | 
				
			||||||
 | 
					import { LEGACY_POLYFILL } from './constants.js'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 修正路径合并 避免在windows下被转义
 | 
				
			||||||
 | 
					export function urlJoin(...args) {
 | 
				
			||||||
 | 
					  return join(...args).replace(/\\/g, '/')
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function uuid() {
 | 
				
			||||||
 | 
					  return randomUUID().slice(-8)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function md5(str = '') {
 | 
				
			||||||
 | 
					  let sum = createHash('md5')
 | 
				
			||||||
 | 
					  sum.update(str, 'utf8')
 | 
				
			||||||
 | 
					  return sum.digest('hex').slice(0, 8)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function gzip(val) {
 | 
				
			||||||
 | 
					  return gzipSync(val)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function friendlyErrors(pathname, ext = '') {
 | 
					export function friendlyErrors(pathname, ext = '') {
 | 
				
			||||||
  console.log(cyan(pathname), red(`not found!!!`))
 | 
					  console.log(cyan(pathname), red(`not found!!!`))
 | 
				
			||||||
| 
						 | 
					@ -7,3 +36,70 @@ export function friendlyErrors(pathname, ext = '') {
 | 
				
			||||||
    blue(`/index.${ext}`)
 | 
					    blue(`/index.${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}\`)
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					    ws.addEventListener('open', function (r) {
 | 
				
			||||||
 | 
					      if(vue_live_hmr.closed){
 | 
				
			||||||
 | 
					        delete vue_live_hmr.closed
 | 
				
			||||||
 | 
					        location.reload()
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      console.log('fite hmr ready...')
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					    ws.addEventListener('close', function(){
 | 
				
			||||||
 | 
					      vue_live_hmr.closed = true
 | 
				
			||||||
 | 
					      if (localStorage.getItem(location.pathname) === '1') {
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      setTimeout(vue_live_hmr, 2000)
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    ws.addEventListener('message', function (ev) {
 | 
				
			||||||
 | 
					      var { action, data } = JSON.parse(ev.data)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					      switch (action) {
 | 
				
			||||||
 | 
					        case 'reload':
 | 
				
			||||||
 | 
					          location.reload()
 | 
				
			||||||
 | 
					          break
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					        case 'render':
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            ${
 | 
				
			||||||
 | 
					              legacy
 | 
				
			||||||
 | 
					                ? `
 | 
				
			||||||
 | 
					            let stylesheet = document.head.children.namedItem(data.path)
 | 
				
			||||||
 | 
					            if (stylesheet) {
 | 
				
			||||||
 | 
					              stylesheet.textContent = data.content
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            `
 | 
				
			||||||
 | 
					                : `
 | 
				
			||||||
 | 
					            let tmp = [...document.adoptedStyleSheets]
 | 
				
			||||||
 | 
					            for (let i = -1, it; (it = tmp[++i]); ) {
 | 
				
			||||||
 | 
					              if (it.path === data.path) {
 | 
				
			||||||
 | 
					                let stylesheet = new CSSStyleSheet()
 | 
				
			||||||
 | 
					                stylesheet.path = data.path
 | 
				
			||||||
 | 
					                stylesheet.replaceSync(data.content)
 | 
				
			||||||
 | 
					                tmp[i] = stylesheet
 | 
				
			||||||
 | 
					                document.adoptedStyleSheets = tmp
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            `
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          break
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    ${LEGACY_POLYFILL}
 | 
				
			||||||
 | 
					  })()
 | 
				
			||||||
 | 
					  `
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 默认的 web components 判断
 | 
				
			||||||
 | 
					export function defaultCustomElement(tag) {
 | 
				
			||||||
 | 
					  return tag.startsWith('wc-')
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										36
									
								
								lib/ws.js
								
								
								
								
							
							
						
						
									
										36
									
								
								lib/ws.js
								
								
								
								
							| 
						 | 
					@ -6,28 +6,40 @@
 | 
				
			||||||
import { WebSocketServer } from 'ws'
 | 
					import { WebSocketServer } from 'ws'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class WebSocket {
 | 
					class WebSocket {
 | 
				
			||||||
  #ws = null // ws实例
 | 
					  #clients = new Map()
 | 
				
			||||||
  #queue = [] // 消息队列
 | 
					  #queue = [] // 消息队列
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(server) {
 | 
					  constructor(server) {
 | 
				
			||||||
    if (server.listening) {
 | 
					    if (server.listening) {
 | 
				
			||||||
      let conn = new WebSocketServer({ server, path: '/ws-vue-live' })
 | 
					      let conn = new WebSocketServer({ server, path: '/ws-fite-hmr' })
 | 
				
			||||||
      conn.on('connection', ws => {
 | 
					      conn.on('connection', (client, req) => {
 | 
				
			||||||
        this.#ws = ws
 | 
					        let params = new URLSearchParams(req.url.slice(req.url.indexOf('?')))
 | 
				
			||||||
        // ws.on('message', data => {
 | 
					        let session = params.get('session')
 | 
				
			||||||
        //   console.log(data + '');
 | 
					        let lock = +params.get('lock')
 | 
				
			||||||
        // })
 | 
					
 | 
				
			||||||
        while (this.#queue.length) {
 | 
					        if (lock === 1) {
 | 
				
			||||||
          let msg = this.#queue.shift()
 | 
					          client.close()
 | 
				
			||||||
          this.send(msg)
 | 
					        } else {
 | 
				
			||||||
 | 
					          this.#clients.set(session, client)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          client.once('close', _ => {
 | 
				
			||||||
 | 
					            this.#clients.delete(session)
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          while (this.#queue.length) {
 | 
				
			||||||
 | 
					            let msg = this.#queue.shift()
 | 
				
			||||||
 | 
					            this.send(msg)
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  send(msg = {}) {
 | 
					  send(msg = {}) {
 | 
				
			||||||
    if (this.#ws) {
 | 
					    if (this.#clients.size) {
 | 
				
			||||||
      this.#ws.send(JSON.stringify(msg))
 | 
					      for (let [key, client] of this.#clients) {
 | 
				
			||||||
 | 
					        client.send(JSON.stringify(msg))
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      this.#queue.push(msg)
 | 
					      this.#queue.push(msg)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  "name": "fite",
 | 
					  "name": "fite",
 | 
				
			||||||
  "type": "module",
 | 
					  "type": "module",
 | 
				
			||||||
  "version": "0.6.0",
 | 
					  "version": "1.4.4",
 | 
				
			||||||
  "bin": {
 | 
					  "bin": {
 | 
				
			||||||
    "fite": "index.js"
 | 
					    "fite": "index.js"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
| 
						 | 
					@ -19,5 +19,6 @@
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "engines": {
 | 
					  "engines": {
 | 
				
			||||||
    "node": ">=16.6.0"
 | 
					    "node": ">=16.6.0"
 | 
				
			||||||
  }
 | 
					  },
 | 
				
			||||||
 | 
					  "license": "MIT"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue