Skip to content

Commit

Permalink
perf: reduce package size. (#18)
Browse files Browse the repository at this point in the history
* chore: remove sirv

* chore: clean code

* perf: analyzer process module logic

* chore:switch open to  opener

* fix: error condition
  • Loading branch information
nonzzz committed Mar 20, 2024
1 parent 14d9332 commit d5965b4
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 333 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"@geist-ui/icons": "^1.0.2",
"@stylexjs/stylex": "^0.5.1",
"@types/node": "^20.7.0",
"@types/opener": "^1.4.3",
"@types/react": "^18.2.31",
"@types/react-dom": "18.2.7",
"@vitejs/plugin-react": "^4.2.1",
Expand Down Expand Up @@ -112,9 +113,8 @@
"esbuild-minify-templates@^0.11.0": "patch:esbuild-minify-templates@npm%3A0.11.0#./.yarn/patches/esbuild-minify-templates-npm-0.11.0-458ab522a4.patch"
},
"dependencies": {
"open": "^9.1.0",
"opener": "^1.5.2",
"picocolors": "^1.0.0",
"sirv": "^2.0.3",
"source-map": "^0.7.4"
},
"files": [
Expand Down
35 changes: 3 additions & 32 deletions src/server/analyzer-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,18 +82,6 @@ export class AnalyzerNode {
imports.forEach((imp) => this.imports.add(imp))
}

private traverse(nodes: Array<GroupWithNode>, handler: (node: GroupWithNode) => void) {
for (const node of nodes) {
if (node.groups && node.groups.length) {
this.traverse(node.groups, handler)
handler(node)
}
if (node.groups && node.groups.length === 0) {
delete (node as any).groups
}
}
}

async setup(bundle: WrappedChunk, pluginContext: PluginContext, compress: ReturnType<typeof createGzip>, workspaceRoot: string) {
const { imports, dynamicImports, map, moduleIds } = bundle
this.addImports(...imports, ...dynamicImports)
Expand Down Expand Up @@ -144,27 +132,10 @@ export class AnalyzerNode {
}

stats.mergePrefixSingleDirectory()
stats.walk(stats.root, (id, n, p) => {
const { meta, groups } = pick(n, ['groups', 'meta'])
p.groups.push({ id, label: id, ...meta, groups })
})

stats.walk(stats.root, (c, p) => p.groups.push(c))
sources.mergePrefixSingleDirectory()
sources.walk(sources.root, (id, n, p) => {
const { meta, groups } = pick(n, ['groups', 'meta'])
p.groups.push({ id, label: id, ...meta, groups })
})
this.traverse(stats.root.groups, (node) => {
node.statSize = node.groups.reduce((acc, cur) => acc + cur.statSize, 0)
})
this.traverse(sources.root.groups, (node) => {
const size = node.groups.reduce((acc, cur) => {
acc.parsedSize += cur.parsedSize
acc.gzipSize += cur.gzipSize
return acc
}, { parsedSize: 0, gzipSize: 0 })
Object.assign(node, size)
})
sources.walk(sources.root, (c, p) => p.groups.push(c))

this.stats = stats.root.groups
this.source = sources.root.groups
// Fix correect size
Expand Down
7 changes: 4 additions & 3 deletions src/server/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import path from 'path'
import fsp from 'fs/promises'
import opener from 'opener'
import type { Logger, Plugin } from 'vite'
import colors from 'picocolors'
import { name } from '../../package.json'
Expand All @@ -12,8 +13,8 @@ import { convertBytes } from './shared'

const isCI = !!process.env.CI

async function openBrowser(address: string) {
await import('open').then((module) => module.default(address, { newInstance: true })).catch(() => {})
function openBrowser(address: string) {
opener(address)
}

const formatNumber = (number: number | string) => colors.dim(colors.bold(number))
Expand Down Expand Up @@ -152,7 +153,7 @@ function analyzer(opts: AnalyzerPluginOptions = { analyzerMode: 'server', summar
setup(foamModule, { title: reportTitle, mode: 'stat' })
if ((opts.openAnalyzer ?? true) && !isCI) {
const address = `http://localhost:${port}`
await openBrowser(address)
openBrowser(address)
}
break
}
Expand Down
80 changes: 49 additions & 31 deletions src/server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,67 @@ import http from 'http'
import fs from 'fs'
import type { AddressInfo } from 'net'
import path from 'path'
import sirv from 'sirv'
import { clientPath, injectHTMLTag } from './shared'
import type { Foam } from './interface'
import type { RenderOptions } from './render'

const assets = sirv(clientPath, { dotfiles: true })
const mimeTypes: Record<string, string> = {
'.js': 'application/javascript',
'.css': 'text/css'
}

export function createServer(port = 0) {
const server = http.createServer()
server.on('request', (req, res) => {
assets(req, res, () => {
res.statusCode = 404
res.end('File not found')
function createStaticMiddleware(options: RenderOptions, foamModule: Foam[]) {
const cache: Map<string, { data: Buffer | string, mimeType: string }> = new Map()

return function staticMiddleware(req: http.IncomingMessage, res: http.ServerResponse) {
const filePath = path.join(clientPath, req.url!)
if (cache.has(filePath)) {
const { data, mimeType } = cache.get(filePath)!
res.writeHead(200, { 'Content-Type': mimeType })
res.end(data)
return
}
if (req.url === '/') {
res.setHeader('Content-Type', 'text/html; charset=utf8;')
let html = fs.readFileSync(path.join(clientPath, 'index.html'), 'utf8')
html = html.replace(/<title>(.*?)<\/title>/, `<title>${options.title}</title>`)
html = injectHTMLTag({
html,
injectTo: 'body',
descriptors: [`<script>
window.defaultSizes = ${JSON.stringify(options.mode)};\n
window.foamModule = ${JSON.stringify(foamModule)};\n
</script>`]
})
res.end(html)
cache.set(filePath, { data: html, mimeType: 'text/html; charset=utf8;' })
return
}

fs.readFile(filePath, (err, data) => {
if (err) {
res.statusCode = 404
res.end('Not Found')
} else {
const ext = path.extname(filePath)
const mimeType = mimeTypes[ext] || 'application/octet-stream'
res.writeHead(200, { 'Content-Type': mimeType })
res.end(data)
cache.set(filePath, { data, mimeType })
}
})
})
}
}

export function createServer(port = 0) {
const server = http.createServer()

server.listen(port, () => {
console.log(`server run on http://localhost:${(server.address() as AddressInfo).port}`)
})

const setup = (foamModule: Foam[], options: RenderOptions) => {
server.on('listening', () => {
const previousListerner = server.listeners('request')
server.removeAllListeners('request')
server.on('request', (req, res) => {
if (req.url === '/') {
res.setHeader('Content-Type', 'text/html; charset=utf8;')
let html = fs.readFileSync(path.join(clientPath, 'index.html'), 'utf8')
html = html
.replace(/<title>(.*?)<\/title>/, `<title>${options.title}</title>`)
html = injectHTMLTag({
html,
injectTo: 'body',
descriptors: [`<script>
window.defaultSizes = ${JSON.stringify(options.mode)};\n
window.foamModule = ${JSON.stringify(foamModule)};\n
</script>`] })
res.end(html)
} else {
previousListerner.map(listen => listen.call(server, req, res))
}
})
})
server.on('request', createStaticMiddleware(options, foamModule))
}

return {
Expand Down
73 changes: 32 additions & 41 deletions src/server/source-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { SourceMapConsumer } from 'source-map'
import type { MappingItem } from 'source-map'
import type { ChunkMetadata } from './trie'

const decoder = new TextDecoder()

export async function convertSourcemapToContents(rawSourceMap: string) {
const consumer = await new SourceMapConsumer(rawSourceMap)
const sources = await consumer.sources
Expand Down Expand Up @@ -33,43 +35,43 @@ function splitBytesByNewLine(bytes: Uint8Array) {
return result
}

// serialize external mappings means
// split code by char '\n' and resotre the finally raw code
// we convert javaScript String to unit8Array but like chinese characters and the like may occupy more bytes if they are encoded in UTF8.
// we convert javaScript String to unit8Array
// But like chinese characters and the like may occupy more bytes if they are encoded in UTF8.
// So we should respect the generated column (Because javaScript String are encoded according to UTF16)
function getStringFromSerializeMappings(bytes: Uint8Array[], mappings: Array<Loc>, decoder: TextDecoder) {
const mappingsWithLine: Record<number, Array<Loc>> = {}
let parsedString = ''
for (const mapping of mappings) {
const { generatedLine } = mapping
if (!(generatedLine in mappingsWithLine)) {
mappingsWithLine[generatedLine] = []
function getStringFromSerializeMappings(bytes: Uint8Array[], mappings: Array<Loc>) {
const maapingWithLine = mappings.reduce((acc, cur) => {
const { generatedLine } = cur
if (!(generatedLine in acc)) {
acc[generatedLine] = []
}
mappingsWithLine[generatedLine].push(mapping)
}
for (const line in mappingsWithLine) {
acc[generatedLine].push(cur)
return acc
}, {} as Record<number, Array<Loc>>)

let s = ''
for (const line in maapingWithLine) {
const l = parseInt(line) - 1
if (bytes[l]) {
const runes = decoder.decode(bytes[l])
const mappings = mappingsWithLine[line]
const mappings = maapingWithLine[line]
const cap = mappings.length
for (let i = 0; i < cap; i++) {
const currentMaaping = mappings[i]
const nextMapping = i + 1 > cap ? null : mappings[i + 1]
if (cap === 1 || typeof currentMaaping.lastGeneratedColumn === 'object') {
parsedString += runes.substring(currentMaaping.generatedColumn)
const nextMapping = i + 1 >= cap ? null : mappings[i + 1]
if (cap === 1 || currentMaaping.lastGeneratedColumn === null) {
s += runes.substring(currentMaaping.generatedColumn)
continue
}
if (typeof currentMaaping.lastGeneratedColumn === 'number') {
const end = currentMaaping.lastGeneratedColumn + 1 === nextMapping?.generatedColumn
? nextMapping.generatedColumn
: currentMaaping.lastGeneratedColumn
parsedString += runes.substring(currentMaaping.generatedColumn, end)
s += runes.substring(currentMaaping.generatedColumn, end)
}
}
}
}
return parsedString
return s
}

// https://esbuild.github.io/faq/#minified-newlines
Expand All @@ -79,34 +81,23 @@ function getStringFromSerializeMappings(bytes: Uint8Array[], mappings: Array<Loc
export async function getSourceMappings(code: Uint8Array, rawSourceMap: string, formatter: (id: string) => string) {
const hints: Record<string, string> = {}
const bytes = splitBytesByNewLine(code)
const promises: Array<[() => string, MappingItem]> = []
const decoder = new TextDecoder()
const consumer = await new SourceMapConsumer(rawSourceMap)
const mappingWithId: Record<string, { mappings: Array<Loc> }> = Object.create(null)
consumer.eachMapping(mapping => {
if (mapping.source) promises.push([() => formatter(mapping.source), mapping])
}, null, SourceMapConsumer.ORIGINAL_ORDER)

const mappings = promises.map(([fn, mapping]) => {
const id = fn()
return { mapping, id }
})

const sortedMappings = mappings.reduce((acc, cur) => {
if (!acc[cur.id]) {
acc[cur.id] = {
mappings: []
if (mapping.source) {
const id = formatter(mapping.source)
if (!(id in mappingWithId)) {
mappingWithId[id] = { mappings: [] }
}
mappingWithId[id].mappings.push(mapping as Loc)
}
acc[cur.id].mappings.push(cur.mapping as any)
return acc
}, {} as Record<string, { mappings: Array<Loc> }>)
}, null, SourceMapConsumer.ORIGINAL_ORDER)

for (const key in sortedMappings) {
sortedMappings[key].mappings.sort((a, b) => a.generatedColumn - b.generatedColumn)
const { mappings } = sortedMappings[key]
for (const id in mappingWithId) {
const { mappings } = mappingWithId[id]
mappings.sort((a, b) => a.generatedColumn - b.generatedColumn)
if (mappings.length > 0) {
const s = getStringFromSerializeMappings(bytes, mappings, decoder)
hints[key] = s
hints[id] = getStringFromSerializeMappings(bytes, mappings)
}
}
consumer.destroy()
Expand Down
23 changes: 21 additions & 2 deletions src/server/trie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,30 @@ export class FileSystemTrie<T> {
}
}

walk(node: Node<T>, handler: (id: string, node: Node<T>, parent: GroupWithNode) => void) {
walk(node: Node<T>, handler: (child: GroupWithNode, parent: GroupWithNode) => any) {
if (!node.children.size) return
for (const [id, childNode] of node.children.entries()) {
handler(id, childNode, node as unknown as GroupWithNode)
const child: GroupWithNode = { ...childNode.meta, id, label: id, groups: childNode.groups }
if (childNode.isEndOfPath) {
delete (child as any).groups
}
handler(child, node as unknown as GroupWithNode)
this.walk(childNode, handler)
if (child.groups && child.groups.length) {
switch (node.kind) {
case 'stat':
child.statSize = child.groups.reduce((acc, cur) => acc + cur.statSize, 0)
break
case 'source':{
const size = child.groups.reduce((acc, cur) => {
acc.parsedSize += cur.parsedSize
acc.gzipSize += cur.gzipSize
return acc
}, { parsedSize: 0, gzipSize: 0 })
Object.assign(child, size)
}
}
}
}
// memory free
node.children.clear()
Expand Down
Loading

0 comments on commit d5965b4

Please sign in to comment.