fite/lib/compile-vue.js

258 lines
6.3 KiB
JavaScript
Raw Normal View History

2022-09-09 10:52:27 +08:00
/**
* {}
* @author yutent<yutent.io@gmail.com>
* @date 2022/09/06 14:43:01
*/
import fs from 'iofs'
import scss from '@bytedo/sass'
2022-10-18 16:02:29 +08:00
import { createHash } from 'crypto'
2023-02-01 10:51:33 +08:00
import Es from 'esbuild'
2022-09-09 10:52:27 +08:00
2023-02-01 10:51:33 +08:00
import {
JS_EXP,
STYLE_EXP,
HTML_EXP,
CSS_SHEET_EXP,
HMR_SCRIPT
} from './constants.js'
2022-09-09 10:52:27 +08:00
const OPTIONS = {
indentType: 'space',
indentWidth: 2
}
2022-10-18 16:02:29 +08:00
function md5(str = '') {
let sum = createHash('md5')
sum.update(str, 'utf8')
return sum.digest('hex').slice(0, 8)
}
2022-11-03 18:22:42 +08:00
function scopeCss(css = '', hash) {
2022-10-18 16:02:29 +08:00
let rules = css.matchAll(CSS_SHEET_EXP)
return [...rules]
.map(r => {
let selector = r[1]
let style = r[2]
selector = selector.split(',')
selector = selector
.map(s => {
let tmp = s.split(' ')
let last = tmp.pop()
if (last.includes(':')) {
last = last.replace(':', `[data-${hash}]:`)
} else {
last += `[data-${hash}]`
}
tmp.push(last)
return tmp.join(' ')
})
.join(', ')
return selector + ` {${style}}`
})
.join('\n')
}
2022-09-09 10:52:27 +08:00
/**
* 编译scss为css
* @param file <String> 文件路径或scss代码
2022-10-18 16:02:29 +08:00
* @param mini <Boolean> 是否压缩
2022-09-09 10:52:27 +08:00
*/
2022-10-18 16:02:29 +08:00
export function compileScss(file, mini = true) {
let style = mini ? 'compressed' : 'expanded'
2022-09-09 10:52:27 +08:00
try {
if (fs.isfile(file)) {
2022-12-29 18:02:25 +08:00
return scss.compile(file, { style, ...OPTIONS }).css.trim()
2022-09-09 10:52:27 +08:00
} else {
2022-12-29 18:02:25 +08:00
return scss.compileString(file, { style, ...OPTIONS }).css.trim()
2022-09-09 10:52:27 +08:00
}
} catch (err) {
console.error(err)
}
}
/**
* 解析js
* 主要是处理js的依赖引用
* @param code <String> js代码
*/
2023-01-13 11:40:53 +08:00
export function parseJs(
code = '',
imports,
{ IS_MPA, currentPage } = {},
isBuild
) {
2022-09-09 10:52:27 +08:00
let fixedStyle = '\n\n'
return (
code
2023-01-13 11:40:53 +08:00
.replace(
/import (.*?) from (["'])(.*?)\2/g,
function (m, alias, q, name) {
if (name.startsWith('@/')) {
name = name.replace('@/', '/assets/js/')
}
2022-09-09 10:52:27 +08:00
2023-01-13 11:40:53 +08:00
if (!imports[name]) {
if (name.startsWith('./')) {
name = name.replace('./', '/assets/js/')
if (IS_MPA) {
name += `${currentPage}/`
}
} else if (
name.startsWith('/') &&
!name.startsWith('//') &&
!name.startsWith('/assets/js/')
) {
name = name.replace(/^\//, '/assets/js/')
2022-10-10 15:05:30 +08:00
}
2022-09-09 10:52:27 +08:00
2023-01-13 11:40:53 +08:00
if (!name.endsWith('.js') && !name.endsWith('.vue')) {
if (name.includes('components')) {
name += '.vue'
} else {
name += '.js'
}
2022-09-09 10:52:27 +08:00
}
}
2023-01-13 11:40:53 +08:00
if (isBuild) {
name = name.replace(/\.vue$/, '.js')
}
return `import ${alias} from '${name}'`
2022-09-09 10:52:27 +08:00
}
2023-01-13 11:40:53 +08:00
)
2022-10-11 19:31:04 +08:00
.replace(/import\((['"])(.*?)\1\)/g, function (m, q, name) {
if (isBuild) {
name = name.replace(/\.vue$/, '.js')
}
return `import('${name}')`
})
2022-09-09 10:52:27 +08:00
.replace(/import (["'])(.*?)\1/g, function (m, q, name) {
if (name.endsWith('.css') || name.endsWith('.scss')) {
if (name.startsWith('@/')) {
2023-01-31 19:17:38 +08:00
name = name.replace('@/', '/')
}
if (isBuild) {
name = name.replace(/\.scss/, '.css')
2022-09-09 10:52:27 +08:00
}
let tmp = `style${Date.now()}`
fixedStyle += `document.adoptedStyleSheets.push(${tmp})\n`
2023-02-02 16:44:18 +08:00
// 修正那反人类的windows路径
2023-02-02 16:39:31 +08:00
return `import ${tmp} from '${name}' assert { type: 'css' }\n${tmp}.path = '${name.replace(
/\\/g,
'\\\\'
)}'`
2022-09-09 10:52:27 +08:00
} else {
if (name.startsWith('@/')) {
2022-10-10 15:05:30 +08:00
name = name.replace('@/', '/assets/js/')
2022-09-09 10:52:27 +08:00
}
2022-10-11 19:31:04 +08:00
//
2022-10-10 15:05:30 +08:00
if (!imports[name]) {
2022-09-09 10:52:27 +08:00
if (!name.startsWith('/') && !name.startsWith('./')) {
name = '/' + name
}
if (!name.endsWith('.js') && !name.endsWith('.vue')) {
name += '.js'
}
}
return `import '${name}'`
}
}) + fixedStyle
)
}
/**
* 将vue转为js
* @param file <String> 文件路径
* @return <String> 返回转换后的js代码
*/
2022-10-11 19:31:04 +08:00
export function compileVue(file, imports, options = {}, isBuild) {
2022-09-09 10:52:27 +08:00
let code = (fs.cat(file) || '').toString()
2023-01-31 19:17:38 +08:00
let CACHE = options.CACHE || {}
2022-09-09 10:52:27 +08:00
let js = code.match(JS_EXP)
let scss = code.matchAll(STYLE_EXP)
let html = code.match(HTML_EXP)
2022-10-18 16:02:29 +08:00
let hash = md5(file)
2022-09-09 10:52:27 +08:00
// console.log(typeof scss)
2022-10-18 16:02:29 +08:00
scss = [...scss].map(it => [it[0], it[1]])
2022-09-09 10:52:27 +08:00
js = js ? js[1] : ''
2023-01-31 19:17:38 +08:00
2022-09-09 10:52:27 +08:00
html = (html ? html[1] : '').replace(/`/g, '\\`').replace(/\$\{/g, '\\${')
2022-10-18 16:02:29 +08:00
html = html.replace(/<([\w\-]+)([^>]*?)>/g, `<$1 data-${hash} $2>`)
2022-09-09 10:52:27 +08:00
2023-01-31 19:17:38 +08:00
if (CACHE[file]) {
CACHE[file] = {
changed: CACHE[file].js !== js || CACHE[file].html !== html,
js,
html
}
} else {
CACHE[file] = { changed: false, js, html }
}
2022-10-11 19:31:04 +08:00
js = parseJs(js, imports, options, isBuild).replace(
2022-09-09 10:52:27 +08:00
'export default {',
2023-01-31 19:17:38 +08:00
`export default {\n template: \`${html}\`,`
2022-09-09 10:52:27 +08:00
)
if (scss.length) {
2022-10-18 16:02:29 +08:00
scss = scss.map(it => {
let scoped = it[0].includes('scoped')
let css = compileScss(it[1])
if (scoped) {
return scopeCss(css, hash)
}
return css
})
2023-01-31 19:17:38 +08:00
CACHE[file].css = scss.join(' ')
2022-10-18 16:02:29 +08:00
2023-02-02 16:44:18 +08:00
// 修正那反人类的windows路径
2022-10-18 16:02:29 +08:00
js += `
2022-12-29 18:02:25 +08:00
let stylesheet = new CSSStyleSheet()
2023-02-02 16:39:31 +08:00
stylesheet.path = '${file
.slice(options.IS_MPA ? options.pagesDir.length : options.root.length)
.replace(/\\/g, '\\\\')}'
2023-01-31 19:17:38 +08:00
stylesheet.replaceSync(\`${CACHE[file].css}\`)
2022-12-29 18:02:25 +08:00
document.adoptedStyleSheets.push(stylesheet)
`
2022-09-09 10:52:27 +08:00
}
return js
}
2022-10-11 19:31:04 +08:00
/**
* 解析模板html
*/
2023-01-31 19:17:38 +08:00
export function parseHtml(html, { page, imports, entry }, isBuild = false) {
2022-10-11 19:31:04 +08:00
return html
.replace(
'</head>',
2023-01-31 19:17:38 +08:00
` <script>window.process = {env: {NODE_ENV: '${
isBuild ? 'production' : 'development'
}'}}</script>\n${
2023-02-01 10:51:33 +08:00
isBuild
? ''
: ` <script>${
Es.transformSync(HMR_SCRIPT, { minify: true }).code
}</script>`
2023-01-31 19:17:38 +08:00
}</head>`
2022-10-11 19:31:04 +08:00
)
.replace('{{title}}', page.title || '')
.replace('{{keywords}}', page.keywords || '')
.replace('{{description}}', page.description || '')
.replace('{{importmap}}', JSON.stringify({ imports }))
2023-01-13 11:40:53 +08:00
.replace(
'<script src="main.js"></script>',
`<script type="module">\n${entry}\n</script>`
)
2022-10-11 19:31:04 +08:00
}