feat(build): replace gulp static pipeline with vite

This commit is contained in:
Mega Yu
2026-04-28 14:14:41 +08:00
parent 3784a81801
commit 8bf4624a34
13 changed files with 520 additions and 1218 deletions

View File

@@ -135,7 +135,7 @@ jobs:
echo "ENABLE_FILE_SYNC_PRODUCTION=${{ github.event_name == 'schedule' || github.event.inputs.enable-file-sync-production == 'true' }}" >> $GITHUB_ENV
- name: Compile CLJS
run: pnpm install --frozen-lockfile && gulp build && pnpm cljs:release-electron && pnpm vite:workers-build
run: pnpm install --frozen-lockfile && pnpm static:build && pnpm cljs:release-electron && pnpm vite:workers-build
env:
LOGSEQ_SENTRY_DSN: ${{ secrets.LOGSEQ_SENTRY_DSN }}
LOGSEQ_POSTHOG_TOKEN: ${{ secrets.LOGSEQ_POSTHOG_TOKEN }}

View File

@@ -90,7 +90,7 @@ jobs:
# NOTE: require the app to be build with DEV-RELEASE flag
- name: Prepare E2E test build
run: |
pnpm gulp:build && clojure -M:cljs release app db-worker --config-merge "{:closure-defines {frontend.config/DEV-RELEASE true}}" --debug && pnpm vite:app-build && pnpm vite:workers-build
pnpm static:build && clojure -M:cljs release app db-worker --config-merge "{:closure-defines {frontend.config/DEV-RELEASE true}}" --debug && pnpm vite:app-build && pnpm vite:workers-build
- name: Run e2e tests
run: cd clj-e2e && timeout 30m bb dev

View File

@@ -91,7 +91,7 @@ jobs:
# NOTE: require the app to be build with DEV-RELEASE flag
- name: Prepare E2E test build
run: |
pnpm gulp:build && clojure -M:cljs release app db-worker --config-merge "{:closure-defines {frontend.config/DEV-RELEASE true}}" --debug && pnpm vite:app-build && pnpm vite:workers-build
pnpm static:build && clojure -M:cljs release app db-worker --config-merge "{:closure-defines {frontend.config/DEV-RELEASE true}}" --debug && pnpm vite:app-build && pnpm vite:workers-build
- name: Upload build artifact
uses: actions/upload-artifact@v4

View File

@@ -55,7 +55,7 @@ jobs:
- name: Build web + workers
run: |
pnpm gulp:build
pnpm static:build
clojure -M:cljs release app db-worker \
--config-merge '{:compiler-options {:source-map true :source-map-include-sources-content true :source-map-detail-level :symbols}}'
pnpm vite:app-build

View File

@@ -61,7 +61,7 @@ jobs:
- name: Build Released-Web
run: |
pnpm gulp:build && clojure -M:cljs release app db-worker --config-merge '{:compiler-options {:source-map true :source-map-include-sources-content true :source-map-detail-level :symbols}}' && pnpm vite:app-build && pnpm vite:workers-build
pnpm static:build && clojure -M:cljs release app db-worker --config-merge '{:compiler-options {:source-map true :source-map-include-sources-content true :source-map-detail-level :symbols}}' && pnpm vite:app-build && pnpm vite:workers-build
rsync -avz --exclude node_modules --exclude android --exclude ios --exclude mobile ./static/ ./public/
ls -lR ./public && mkdir r2 && mv ./public/js/main.js.map ./r2/db-test.main.js.map
sed -i 's/=main.js.map/=https:\/\/assets.logseq.io\/db-test.main.js.map/g' ./public/js/main.js

View File

@@ -55,7 +55,7 @@ jobs:
- name: Build web + workers
run: |
pnpm gulp:build
pnpm static:build
clojure -M:cljs release app db-worker \
--config-merge '{:compiler-options {:source-map true :source-map-include-sources-content true :source-map-detail-level :symbols}}'
pnpm vite:app-build

View File

@@ -1,317 +0,0 @@
const fs = require('fs')
const utils = require('util')
const cp = require('child_process')
const exec = utils.promisify(cp.exec)
const path = require('path')
const gulp = require('gulp')
const replace = require('gulp-replace')
const outputPath = path.join(__dirname, 'static')
const outputJsPath = path.join(outputPath, 'js')
const resourcesPath = path.join(__dirname, 'resources')
const publicRootPath = path.join(__dirname, 'public')
const mobilePath = path.join(outputPath, 'mobile')
const mobileJsPath = path.join(mobilePath, 'js')
const sourcePath = path.join(__dirname, 'src/main/frontend')
const resourceFilePath = path.join(resourcesPath, '**')
const outputFilePath = path.join(outputPath, '**')
const rawCopySrc = (globs, options = {}) =>
gulp.src(globs, { encoding: false, ...options })
const staticCleanKeep = new Set([
'entitlements.plist',
'node_modules',
'package.json',
'pnpm-lock.yaml',
])
const css = {
watchCSS () {
return cp.spawn(`pnpm css:watch`, {
shell: true,
stdio: 'inherit',
})
},
watchMobileCSS () {
return cp.spawn(`pnpm css:mobile-watch`, {
shell: true,
stdio: 'inherit',
})
},
buildCSS (...params) {
return gulp.series(
() => exec(`pnpm css:build`, {}),
css._optimizeCSSForRelease,
)(...params)
},
buildMobileCSS (...params) {
return gulp.series(
() => exec(`pnpm css:mobile-build`, {}),
)(...params)
},
_optimizeCSSForRelease () {
return gulp.src(path.join(outputPath, 'css', 'style.css')).
pipe(gulp.dest(path.join(outputPath, 'css')))
},
}
const common = {
clean () {
if (!fs.existsSync(outputPath)) {
fs.mkdirSync(outputPath, { recursive: true })
}
for (const entry of fs.readdirSync(outputPath)) {
if (staticCleanKeep.has(entry)) continue
fs.rmSync(path.join(outputPath, entry), {
recursive: true,
force: true,
maxRetries: 10,
retryDelay: 100,
})
}
return Promise.resolve()
},
syncResourceFile () {
return rawCopySrc(resourceFilePath).pipe(gulp.dest(outputPath))
},
// NOTE: All assets from node_modules are copied to the output directory
syncAssetFiles (...params) {
return gulp.series(
() => rawCopySrc([
'node_modules/katex/dist/katex.min.js',
'node_modules/katex/dist/contrib/mhchem.min.js',
'node_modules/html2canvas/dist/html2canvas.min.js',
'node_modules/interactjs/dist/interact.min.js',
'node_modules/photoswipe/dist/umd/*.js',
'node_modules/marked/lib/marked.umd.js',
'node_modules/@highlightjs/cdn-assets/highlight.min.js',
'node_modules/@isomorphic-git/lightning-fs/dist/lightning-fs.min.js',
'packages/ui/dist/ui.js',
'node_modules/@sqlite.org/sqlite-wasm/dist/sqlite3.wasm',
'node_modules/react/umd/react.production.min.js',
'node_modules/react/umd/react.development.js',
'node_modules/react-dom/umd/react-dom.production.min.js',
'node_modules/react-dom/umd/react-dom.development.js',
'node_modules/prop-types/prop-types.min.js',
'node_modules/dompurify/dist/purify.js',
]).pipe(gulp.dest(path.join(outputPath, 'js'))),
() => gulp.src([
'node_modules/@tabler/icons-react/dist/umd/tabler-icons-react.min.js',
]).
pipe(replace('"@tabler/icons-react"]={},a.react,',
'"tablerIcons"]={},a.React,')).
pipe(gulp.dest(path.join(outputPath, 'js'))),
() => rawCopySrc([
'node_modules/@glidejs/glide/dist/glide.min.js',
'node_modules/@glidejs/glide/dist/css/glide.core.min.css',
'node_modules/@glidejs/glide/dist/css/glide.theme.min.css',
]).pipe(gulp.dest(path.join(outputPath, 'js', 'glide'))),
() => rawCopySrc([
'node_modules/pdfjs-dist/legacy/build/pdf.mjs',
'node_modules/pdfjs-dist/legacy/build/pdf.worker.mjs',
'node_modules/pdfjs-dist/legacy/web/pdf_viewer.mjs',
]).pipe(gulp.dest(path.join(outputPath, 'js', 'pdfjs'))),
() => rawCopySrc([
'node_modules/pdfjs-dist/cmaps/*.*',
]).pipe(gulp.dest(path.join(outputPath, 'js', 'pdfjs', 'cmaps'))),
() => rawCopySrc([
'node_modules/inter-ui/inter.css',
]).pipe(gulp.dest(path.join(outputPath, 'css'))),
() => rawCopySrc('node_modules/inter-ui/web/*.*').
pipe(gulp.dest(path.join(outputPath, 'css', 'web'))),
() => rawCopySrc([
'node_modules/katex/dist/fonts/*.woff2',
]).pipe(gulp.dest(path.join(outputPath, 'css', 'fonts'))),
() => rawCopySrc([
'node_modules/katex/dist/katex.min.js',
'node_modules/katex/dist/contrib/mhchem.min.js',
'node_modules/marked/lib/marked.umd.js',
'node_modules/@highlightjs/cdn-assets/highlight.min.js',
'node_modules/@isomorphic-git/lightning-fs/dist/lightning-fs.min.js',
'node_modules/react/umd/react.production.min.js',
'node_modules/react/umd/react.development.js',
'node_modules/react-dom/umd/react-dom.production.min.js',
'node_modules/react-dom/umd/react-dom.development.js',
'node_modules/prop-types/prop-types.min.js',
'node_modules/interactjs/dist/interact.min.js',
'node_modules/photoswipe/dist/umd/*.js',
'packages/ui/dist/ui.js',
'node_modules/@sqlite.org/sqlite-wasm/dist/sqlite3.wasm',
]).pipe(gulp.dest(path.join(outputPath, 'mobile', 'js'))),
() => rawCopySrc([
'node_modules/inter-ui/inter.css',
]).pipe(gulp.dest(path.join(outputPath, 'mobile', 'css'))),
() => rawCopySrc('node_modules/inter-ui/web/*.*').
pipe(gulp.dest(path.join(outputPath, 'mobile', 'css', 'web'))),
() => rawCopySrc([
'node_modules/katex/dist/fonts/*.woff2',
]).pipe(gulp.dest(path.join(outputPath, 'mobile', 'css', 'fonts'))),
)(...params)
},
keepSyncResourceFile () {
return gulp.watch(resourceFilePath, { ignoreInitial: true },
common.syncResourceFile)
},
syncAllStatic () {
return rawCopySrc([
outputFilePath,
'!' + path.join(outputPath, 'node_modules/**'),
'!' + path.join(outputPath, 'mobile/**'),
'!' + path.join(outputPath, 'android/**'),
'!' + path.join(outputPath, 'ios/**'),
]).pipe(gulp.dest(publicRootPath))
},
syncJS_CSSinRt () {
return gulp.src([
path.join(outputPath, 'js/**'),
path.join(outputPath, 'css/**'),
], { base: outputPath }).pipe(gulp.dest(publicRootPath))
},
keepSyncStaticInRt () {
return gulp.watch([
path.join(outputPath, 'js/**'),
path.join(outputPath, 'css/**'),
], { ignoreInitial: true }, common.syncJS_CSSinRt)
},
syncWorkersToMobile () {
return gulp.src([
path.join(outputPath, 'js/db-worker.js'),
], { base: outputJsPath }).pipe(gulp.dest(mobileJsPath))
},
keepSyncWorkersToMobile () {
return gulp.watch([
path.join(outputPath, 'js/db-worker.js'),
], { ignoreInitial: false }, common.syncWorkersToMobile)
},
async runCapWithLocalDevServerEntry (cb) {
const mode = process.env.PLATFORM || 'ios'
const LOGSEQ_APP_SERVER_URL = `http://localhost:3002`
if (typeof global.fetch === 'function') {
try {
await fetch(LOGSEQ_APP_SERVER_URL)
} catch (e) {
return cb(new Error(
`/* ❌ Please check if the service is ON. (${LOGSEQ_APP_SERVER_URL}) ❌ */`))
}
}
console.log(`------ Cap ${mode.toUpperCase()} -----`)
console.log(`Dev serve at: ${LOGSEQ_APP_SERVER_URL}`)
console.log(`--------------------------------------`)
cp.execSync(`pnpm exec cap sync ${mode}`, {
stdio: 'inherit',
env: Object.assign(process.env, {
LOGSEQ_APP_SERVER_URL,
}),
})
cp.execSync(`rm -rf ios/App/App/public/out`, {
stdio: 'inherit',
})
cp.execSync(`pnpm exec cap run ${mode}`, {
stdio: 'inherit',
env: Object.assign(process.env, {
LOGSEQ_APP_SERVER_URL,
}),
})
cb()
},
switchReactDevelopmentMode (cb) {
try {
const reactFrom = path.join(outputPath, 'js', 'react.development.js')
const reactTo = path.join(outputPath, 'js', 'react.production.min.js')
fs.renameSync(reactFrom, reactTo)
const reactDomFrom = path.join(outputPath, 'js',
'react-dom.development.js')
const reactDomTo = path.join(outputPath, 'js',
'react-dom.production.min.js')
fs.renameSync(reactDomFrom, reactDomTo)
cb()
} catch (err) {
console.error('Error during switchReactDevelopmentMode:', err)
cb(err)
}
},
}
exports.electron = () => {
if (!fs.existsSync(path.join(outputPath, 'node_modules'))) {
cp.execSync('pnpm install --frozen-lockfile', {
cwd: outputPath,
stdio: 'inherit',
})
}
cp.execSync('pnpm electron:dev', {
cwd: outputPath,
stdio: 'inherit',
})
}
exports.electronMaker = async () => {
cp.execSync('pnpm cljs:release-electron', {
stdio: 'inherit',
})
const pkgPath = path.join(outputPath, 'package.json')
const pkg = require(pkgPath)
const version = fs.readFileSync(
path.join(__dirname, 'src/main/frontend/version.cljs')).
toString().
match(/[0-9.]{3,}/)[0]
if (!version) {
throw new Error('release version error in src/**/*/version.cljs')
}
pkg.version = version
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2))
if (!fs.existsSync(path.join(outputPath, 'node_modules'))) {
cp.execSync('pnpm install --frozen-lockfile', {
cwd: outputPath,
stdio: 'inherit',
})
}
cp.execSync('pnpm electron:make', {
cwd: outputPath,
stdio: 'inherit',
})
}
exports.cap = common.runCapWithLocalDevServerEntry
exports.clean = common.clean
exports.watch = gulp.series(
common.syncResourceFile,
common.syncAssetFiles, common.switchReactDevelopmentMode,
gulp.parallel(common.keepSyncResourceFile, css.watchCSS))
exports.watchMobile = gulp.series(
common.syncResourceFile, common.syncAssetFiles,
gulp.parallel(common.keepSyncResourceFile, common.keepSyncWorkersToMobile, css.watchMobileCSS))
exports.build = gulp.series(common.clean, common.syncResourceFile,
common.syncAssetFiles, css.buildCSS)
exports.buildMobile = gulp.series(common.clean, common.syncResourceFile,
common.syncAssetFiles, css.buildMobileCSS)
exports.syncWorkersToMobile = common.syncWorkersToMobile

View File

@@ -19,13 +19,9 @@
"better-sqlite3": "^12.8.0",
"cross-env": "^10.1.0",
"cssnano": "^7.1.3",
"gulp": "^5.0.1",
"gulp-postcss": "^10.0.0",
"gulp-replace": "^1.1.4",
"npm-run-all2": "^8.0.4",
"playwright": "=1.58.2",
"postcss": "^8.5.8",
"postcss-cli": "11.0.1",
"postcss-import": "16.1.1",
"postcss-import-ext-glob": "2.1.1",
"postcss-nested": "7.0.2",
@@ -38,22 +34,30 @@
"tailwindcss-animate": "^1.0.7",
"typescript": "^5.9.3",
"vite": "^8.0.9",
"vite-plugin-static-copy": "4.1.0",
"ws": "^8.19.0"
},
"scripts": {
"watch": "run-p gulp:watch cljs:watch vite:app-watch vite:workers-watch",
"electron-watch": "run-p gulp:watch cljs:electron-watch vite:app-watch vite:workers-watch",
"app-watch": "run-p gulp:watch cljs:app-watch vite:app-watch vite:workers-watch",
"mobile-watch": "run-p gulp:mobile-watch cljs:mobile-watch vite:mobile-watch vite:workers-mobile-watch",
"watch": "run-p static:watch cljs:watch vite:app-watch vite:workers-watch",
"electron-watch": "run-p static:watch cljs:electron-watch vite:app-watch vite:workers-watch",
"app-watch": "run-p static:watch cljs:app-watch vite:app-watch vite:workers-watch",
"mobile-watch": "run-p static:mobile-watch cljs:mobile-watch vite:mobile-watch vite:workers-mobile-watch",
"publishing-watch": "run-p cljs:publishing-watch vite:publishing-watch",
"dev": "run-p gulp:watch gulp:mobile-watch cljs:dev-watch vite:app-watch vite:mobile-watch vite:workers-watch vite:workers-mobile-watch",
"release": "run-s gulp:build cljs:release vite:workers-build",
"release-app": "run-s gulp:build cljs:release-app vite:workers-build",
"release-mobile": "run-s gulp:buildMobile cljs:release-mobile gulp:syncWorkersToMobile vite:workers-mobile-build",
"dev-release-app": "run-s gulp:build cljs:dev-release-app vite:workers-build",
"dev-electron-app": "gulp electron",
"release-electron": "run-s gulp:build && pnpm vite:workers-build && gulp electronMaker",
"dev": "run-p vite:assets-watch:dev vite:css-watch vite:css-mobile-watch cljs:dev-watch vite:app-watch vite:mobile-watch vite:workers-watch vite:workers-mobile-watch",
"release": "run-s static:build cljs:release vite:workers-build",
"release-app": "run-s static:build cljs:release-app vite:workers-build",
"release-mobile": "run-s static:mobile-build cljs:release-mobile static:sync-workers-to-mobile vite:workers-mobile-build",
"dev-release-app": "run-s static:build cljs:dev-release-app vite:workers-build",
"dev-electron-app": "node scripts/static-pipeline.mjs electron",
"release-electron": "run-s static:build electron:make",
"debug-electron": "cd static/ && pnpm electron:debug",
"vite:assets-watch": "pnpm exec vite build --watch --config vite.config.assets.ts",
"vite:assets-watch:dev": "cross-env LOGSEQ_REACT_DEV_ASSETS=true pnpm exec vite build --watch --config vite.config.assets.ts",
"vite:assets-build": "cross-env NODE_ENV=production pnpm exec vite build --config vite.config.assets.ts",
"vite:css-watch": "cross-env TAILWIND_MODE=watch LOGSEQ_CSS_TARGET=desktop pnpm exec vite build --watch --config vite.config.css.ts",
"vite:css-build": "cross-env NODE_ENV=production LOGSEQ_CSS_TARGET=desktop pnpm exec vite build --config vite.config.css.ts",
"vite:css-mobile-watch": "cross-env TAILWIND_MODE=watch LOGSEQ_CSS_TARGET=mobile pnpm exec vite build --watch --config vite.config.css.ts",
"vite:css-mobile-build": "cross-env NODE_ENV=production LOGSEQ_CSS_TARGET=mobile pnpm exec vite build --config vite.config.css.ts",
"vite:workers-watch": "run-s vite:prepare vite:workers-watch:bridge",
"vite:workers-watch:bridge": "pnpm exec vite build --watch --config vite.config.workers.ts",
"vite:workers-build": "run-s vite:prepare vite:workers-build:bridge",
@@ -77,21 +81,22 @@
"vite:publishing-build:bridge": "cross-env NODE_ENV=production pnpm exec vite build --config vite.config.publishing.ts",
"sync-android-release": "pnpm clean && pnpm release-mobile && rm -rf ./static/mobile/**/*.map && pnpm exec cap sync android",
"sync-ios-release": "pnpm clean && pnpm release-mobile && rm -rf ./static/mobile/**/*.map && pnpm exec cap sync ios",
"clean": "gulp clean",
"clean": "node scripts/static-pipeline.mjs clean",
"test": "run-s cljs:test cljs:run-test",
"test:node-adapter": "pnpm --dir deps/db-sync run test:node-adapter",
"report": "run-s cljs:report",
"style:lint": "stylelint \"src/**/*.css\"",
"gulp:watch": "gulp watch",
"gulp:build": "cross-env NODE_ENV=production gulp build",
"gulp:buildMobile": "cross-env NODE_ENV=production gulp buildMobile",
"gulp:syncWorkersToMobile": "gulp syncWorkersToMobile",
"css:build": "postcss tailwind.all.css -o static/css/style.css --verbose --env production",
"css:watch": "cross-env TAILWIND_MODE=watch postcss tailwind.all.css -o static/css/style.css --verbose --watch",
"static:watch": "run-p vite:assets-watch:dev vite:css-watch",
"static:mobile-watch": "run-p vite:assets-watch vite:css-mobile-watch",
"static:build": "run-s clean vite:assets-build vite:css-build",
"static:mobile-build": "run-s clean vite:assets-build vite:css-mobile-build",
"static:sync-workers-to-mobile": "node scripts/static-pipeline.mjs sync-workers-to-mobile",
"electron:make": "node scripts/static-pipeline.mjs electron-maker",
"css:build": "pnpm vite:css-build",
"css:watch": "pnpm vite:css-watch",
"cljs:watch": "clojure -M:cljs watch app db-worker electron",
"gulp:mobile-watch": "gulp watchMobile",
"css:mobile-build": "postcss tailwind.mobile.css -o static/mobile/css/style.css --verbose --env production",
"css:mobile-watch": "cross-env TAILWIND_MODE=watch postcss tailwind.mobile.css -o static/mobile/css/style.css --verbose --watch",
"css:mobile-build": "pnpm vite:css-mobile-build",
"css:mobile-watch": "pnpm vite:css-mobile-watch",
"cljs:mobile-watch": "clojure -M:cljs watch mobile db-worker --config-merge \"{:asset-path \\\"/static/mobile/js\\\" :release {:asset-path \\\"http://localhost\\\"}}\"",
"cljs:release-mobile": "run-s cljs:release-mobile:shadow vite:mobile-build",
"cljs:release-mobile:shadow": "clojure -M:cljs release mobile db-worker --config-merge \"{:asset-path \\\"/static/mobile/js\\\" :release {:asset-path \\\"http://localhost\\\"}}\"",
@@ -120,8 +125,8 @@
"cljs:report": "clojure -M:cljs run shadow.cljs.build-report app db-worker report.html",
"cljs:build-electron": "clojure -A:cljs compile app db-worker electron",
"cljs:lint": "clojure -M:clj-kondo --parallel --lint src --cache false",
"ios:dev": "cross-env PLATFORM=ios gulp cap",
"android:dev": "cross-env PLATFORM=android gulp cap",
"ios:dev": "cross-env PLATFORM=ios node scripts/static-pipeline.mjs cap",
"android:dev": "cross-env PLATFORM=android node scripts/static-pipeline.mjs cap",
"ui:build": "pnpm --dir packages/ui install",
"postinstall": "pnpm ui:build"
},
@@ -194,9 +199,9 @@
"react-transition-group": "4.4.5",
"react-virtuoso": "4.18.3",
"remove-accents": "0.5.0",
"tiny-pinyin": "1.3.2",
"sanitize-filename": "1.6.4",
"send-intent": "^7.0.0",
"tiny-pinyin": "1.3.2",
"url": "^0.11.4",
"util": "^0.12.5"
},

879
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +0,0 @@
module.exports = {
plugins: {
'autoprefixer': {},
'postcss-import-ext-glob': {},
'postcss-import': {},
'tailwindcss/nesting': 'postcss-nested',
tailwindcss: {},
...(process.env.NODE_ENV === 'production' ? { cssnano: {} } : {})
}
}

158
scripts/static-pipeline.mjs Normal file
View File

@@ -0,0 +1,158 @@
import { execFileSync } from 'node:child_process'
import {
copyFileSync,
existsSync,
mkdirSync,
readFileSync,
readdirSync,
renameSync,
rmSync,
writeFileSync,
} from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
const staticDir = path.join(rootDir, 'static')
const staticCleanKeep = new Set([
'entitlements.plist',
'node_modules',
'package.json',
'pnpm-lock.yaml',
])
function run(command, args, options = {}) {
execFileSync(command, args, {
cwd: options.cwd ?? rootDir,
env: { ...process.env, ...(options.env ?? {}) },
shell: process.platform === 'win32',
stdio: 'inherit',
})
}
function clean() {
mkdirSync(staticDir, { recursive: true })
for (const entry of readdirSync(staticDir)) {
if (staticCleanKeep.has(entry)) {
continue
}
rmSync(path.join(staticDir, entry), {
recursive: true,
force: true,
maxRetries: 10,
retryDelay: 100,
})
}
}
function ensureStaticNodeModules() {
if (!existsSync(path.join(staticDir, 'node_modules'))) {
run('pnpm', ['install', '--frozen-lockfile'], { cwd: staticDir })
}
}
function electron() {
ensureStaticNodeModules()
run('pnpm', ['electron:dev'], { cwd: staticDir })
}
function versionFromSource() {
const versionSource = readFileSync(path.join(rootDir, 'src/main/frontend/version.cljs'), 'utf8')
const match = versionSource.match(/[0-9.]{3,}/)
if (!match) {
throw new Error('release version error in src/**/*/version.cljs')
}
return match[0]
}
function electronMaker() {
run('pnpm', ['cljs:release-electron'])
run('pnpm', ['vite:workers-build'])
const packagePath = path.join(staticDir, 'package.json')
const packageJson = JSON.parse(readFileSync(packagePath, 'utf8'))
packageJson.version = versionFromSource()
writeFileSync(packagePath, `${JSON.stringify(packageJson, null, 2)}\n`)
ensureStaticNodeModules()
run('pnpm', ['electron:make'], { cwd: staticDir })
}
function syncWorkersToMobile() {
const source = path.join(staticDir, 'js', 'db-worker.js')
const dest = path.join(staticDir, 'mobile', 'js', 'db-worker.js')
if (!existsSync(source)) {
throw new Error(`Missing worker output: ${source}`)
}
mkdirSync(path.dirname(dest), { recursive: true })
copyFileSync(source, dest)
}
function switchReactDevelopmentMode() {
const pairs = [
['react.development.js', 'react.production.min.js'],
['react-dom.development.js', 'react-dom.production.min.js'],
]
for (const [fromName, toName] of pairs) {
const from = path.join(staticDir, 'js', fromName)
const to = path.join(staticDir, 'js', toName)
if (!existsSync(from)) {
continue
}
rmSync(to, { force: true })
renameSync(from, to)
}
}
async function cap() {
const mode = process.env.PLATFORM || 'ios'
const logseqAppServerUrl = 'http://localhost:3002'
if (typeof global.fetch === 'function') {
try {
await fetch(logseqAppServerUrl)
} catch {
throw new Error(`Please check if the service is ON. (${logseqAppServerUrl})`)
}
}
process.stdout.write(`------ Cap ${mode.toUpperCase()} -----\n`)
process.stdout.write(`Dev serve at: ${logseqAppServerUrl}\n`)
process.stdout.write('--------------------------------------\n')
run('pnpm', ['exec', 'cap', 'sync', mode], {
env: { LOGSEQ_APP_SERVER_URL: logseqAppServerUrl },
})
rmSync(path.join(rootDir, 'ios/App/App/public/out'), { recursive: true, force: true })
run('pnpm', ['exec', 'cap', 'run', mode], {
env: { LOGSEQ_APP_SERVER_URL: logseqAppServerUrl },
})
}
const tasks = {
cap,
clean,
electron,
'electron-maker': electronMaker,
'switch-react-development-mode': switchReactDevelopmentMode,
'sync-workers-to-mobile': syncWorkersToMobile,
}
const taskName = process.argv[2]
const task = tasks[taskName]
if (!task) {
throw new Error(`Unknown static pipeline task: ${taskName}`)
}
await task()

164
vite.config.assets.ts Normal file
View File

@@ -0,0 +1,164 @@
import { normalizePath, type Plugin, defineConfig } from 'vite'
import { viteStaticCopy, type Target } from 'vite-plugin-static-copy'
const useReactDevelopmentAssets = process.env.LOGSEQ_REACT_DEV_ASSETS === 'true'
const virtualEntryId = 'virtual:logseq-assets-entry'
const resolvedVirtualEntryId = `\0${virtualEntryId}`
function virtualAssetsEntryPlugin(): Plugin {
return {
name: 'logseq-assets-entry',
resolveId(id) {
if (id === virtualEntryId) {
return resolvedVirtualEntryId
}
return null
},
load(id) {
if (id === resolvedVirtualEntryId) {
return 'export {}\n'
}
return null
},
generateBundle(_, bundle) {
for (const fileName of Object.keys(bundle)) {
delete bundle[fileName]
}
},
}
}
function flatCopy(src: string | string[], dest: string): Target {
return {
src,
dest,
rename: { stripBase: true },
}
}
function reactRuntimeTargets(dest: string): Target[] {
if (useReactDevelopmentAssets && dest === 'js') {
return [
{
src: 'node_modules/react/umd/react.development.js',
dest,
rename: { stripBase: true, name: 'react.production.min.js' },
},
{
src: 'node_modules/react-dom/umd/react-dom.development.js',
dest,
rename: { stripBase: true, name: 'react-dom.production.min.js' },
},
]
}
return [
flatCopy([
'node_modules/react/umd/react.production.min.js',
'node_modules/react/umd/react.development.js',
'node_modules/react-dom/umd/react-dom.production.min.js',
'node_modules/react-dom/umd/react-dom.development.js',
], dest),
]
}
const desktopJsTargets: Target[] = [
flatCopy([
'node_modules/katex/dist/katex.min.js',
'node_modules/katex/dist/contrib/mhchem.min.js',
'node_modules/html2canvas/dist/html2canvas.min.js',
'node_modules/interactjs/dist/interact.min.js',
'node_modules/photoswipe/dist/umd/*.js',
'node_modules/marked/lib/marked.umd.js',
'node_modules/@highlightjs/cdn-assets/highlight.min.js',
'node_modules/@isomorphic-git/lightning-fs/dist/lightning-fs.min.js',
'packages/ui/dist/ui.js',
'node_modules/@sqlite.org/sqlite-wasm/dist/sqlite3.wasm',
'node_modules/prop-types/prop-types.min.js',
'node_modules/dompurify/dist/purify.js',
], 'js'),
...reactRuntimeTargets('js'),
{
src: 'node_modules/@tabler/icons-react/dist/umd/tabler-icons-react.min.js',
dest: 'js',
rename: { stripBase: true },
transform: {
encoding: 'utf8',
handler(content) {
return content.replace(
'"@tabler/icons-react"]={},a.react,',
'"tablerIcons"]={},a.React,',
)
},
},
},
flatCopy([
'node_modules/@glidejs/glide/dist/glide.min.js',
'node_modules/@glidejs/glide/dist/css/glide.core.min.css',
'node_modules/@glidejs/glide/dist/css/glide.theme.min.css',
], 'js/glide'),
flatCopy([
'node_modules/pdfjs-dist/legacy/build/pdf.mjs',
'node_modules/pdfjs-dist/legacy/build/pdf.worker.mjs',
'node_modules/pdfjs-dist/legacy/web/pdf_viewer.mjs',
], 'js/pdfjs'),
flatCopy('node_modules/pdfjs-dist/cmaps/*.*', 'js/pdfjs/cmaps'),
]
const mobileJsTargets: Target[] = [
flatCopy([
'node_modules/katex/dist/katex.min.js',
'node_modules/katex/dist/contrib/mhchem.min.js',
'node_modules/marked/lib/marked.umd.js',
'node_modules/@highlightjs/cdn-assets/highlight.min.js',
'node_modules/@isomorphic-git/lightning-fs/dist/lightning-fs.min.js',
'node_modules/interactjs/dist/interact.min.js',
'node_modules/photoswipe/dist/umd/*.js',
'packages/ui/dist/ui.js',
'node_modules/@sqlite.org/sqlite-wasm/dist/sqlite3.wasm',
'node_modules/prop-types/prop-types.min.js',
], 'mobile/js'),
...reactRuntimeTargets('mobile/js'),
]
const cssAssetTargets: Target[] = [
flatCopy('node_modules/inter-ui/inter.css', 'css'),
flatCopy('node_modules/inter-ui/web/*.*', 'css/web'),
flatCopy('node_modules/katex/dist/fonts/*.woff2', 'css/fonts'),
flatCopy('node_modules/inter-ui/inter.css', 'mobile/css'),
flatCopy('node_modules/inter-ui/web/*.*', 'mobile/css/web'),
flatCopy('node_modules/katex/dist/fonts/*.woff2', 'mobile/css/fonts'),
]
export default defineConfig({
publicDir: false,
build: {
emptyOutDir: false,
outDir: 'static',
rollupOptions: {
input: virtualEntryId,
},
},
plugins: [
virtualAssetsEntryPlugin(),
viteStaticCopy({
targets: [
{
src: normalizePath('resources/**/*'),
dest: '.',
// Drop only the resources/ prefix while preserving its nested paths.
rename: { stripBase: 1 },
},
...desktopJsTargets,
...mobileJsTargets,
...cssAssetTargets,
],
watch: {
reloadPageOnChange: false,
},
}),
],
})

137
vite.config.css.ts Normal file
View File

@@ -0,0 +1,137 @@
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
import path from 'node:path'
import autoprefixer from 'autoprefixer'
import cssnano from 'cssnano'
import postcss, { type AcceptedPlugin } from 'postcss'
import postcssImport from 'postcss-import'
import postcssImportExtGlob from 'postcss-import-ext-glob'
import tailwindcss from 'tailwindcss'
import tailwindNesting from 'tailwindcss/nesting'
import { defineConfig, type Plugin } from 'vite'
type CssTarget = {
name: string
input: string
output: string
}
const virtualEntryId = 'virtual:logseq-css-entry'
const resolvedVirtualEntryId = `\0${virtualEntryId}`
const allCssTargets: CssTarget[] = [
{
name: 'desktop',
input: 'tailwind.all.css',
output: 'static/css/style.css',
},
{
name: 'mobile',
input: 'tailwind.mobile.css',
output: 'static/mobile/css/style.css',
},
]
function requestedCssTargets(): CssTarget[] {
const targetName = process.env.LOGSEQ_CSS_TARGET
if (!targetName) {
return allCssTargets
}
const target = allCssTargets.find(({ name }) => name === targetName)
if (!target) {
throw new Error(`Unknown LOGSEQ_CSS_TARGET: ${targetName}`)
}
return [target]
}
function filesToWatch(): string[] {
return [
'tailwind.config.js',
...allCssTargets.map(({ input }) => input),
'src',
'resources',
path.join('deps', 'shui', 'src'),
path.join('packages', 'ui', 'src'),
path.join('packages', 'ui', '@'),
].filter(existsSync)
}
async function buildCss(targets: CssTarget[]) {
const postcssPlugin = (plugin: unknown): AcceptedPlugin => plugin as AcceptedPlugin
const postcssPlugins: AcceptedPlugin[] = [
postcssPlugin(postcssImportExtGlob()),
postcssPlugin(postcssImport()),
postcssPlugin(tailwindNesting()),
postcssPlugin(tailwindcss()),
postcssPlugin(autoprefixer()),
]
if (process.env.NODE_ENV === 'production') {
postcssPlugins.push(postcssPlugin(cssnano({ preset: 'default' })))
}
const processor = postcss(postcssPlugins)
for (const target of targets) {
const input = path.resolve(target.input)
const output = path.resolve(target.output)
const result = await processor.process(readFileSync(input, 'utf8'), {
from: input,
to: output,
map: false,
})
mkdirSync(path.dirname(output), { recursive: true })
writeFileSync(output, result.css)
}
}
function cssPipelinePlugin(targets: CssTarget[]): Plugin {
return {
name: 'logseq-css-pipeline',
resolveId(id) {
if (id === virtualEntryId) {
return resolvedVirtualEntryId
}
return null
},
load(id) {
if (id === resolvedVirtualEntryId) {
return 'export {}\n'
}
return null
},
buildStart() {
for (const file of filesToWatch()) {
this.addWatchFile(path.resolve(file))
}
},
async closeBundle() {
await buildCss(targets)
},
generateBundle(_, bundle) {
for (const fileName of Object.keys(bundle)) {
delete bundle[fileName]
}
},
}
}
const cssTargets = requestedCssTargets()
export default defineConfig({
publicDir: false,
build: {
emptyOutDir: false,
outDir: 'static',
rollupOptions: {
input: virtualEntryId,
},
},
plugins: [
cssPipelinePlugin(cssTargets),
],
})