Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for parsing Flow, applied to RN modules by default #17

Merged
merged 4 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,38 @@ Because this extension builds locally, you need to pre-install dependencies.
"type": "string",
"default": "#e33",
"description": "Danger color for the size text"
},
"bundleSize.loader": {
"type": "object",
"patternProperties": {
".*": {
"type": "string",
"enum": [
"base64",
"binary",
"copy",
"css",
"dataurl",
"default",
"empty",
"file",
"js",
"json",
"jsx",
"local-css",
"text",
"ts",
"tsx"
]
}
},
"default": {},
"markdownDescription": "Override the loader in Bundle Size (eg: `.js: jsx` will force `.js` files to be treated as JSX), see [esbuild#loader](https://esbuild.github.io/api/#loader) for more details"
},
"bundleSize.flowPattern": {
"type": "string",
"default": "\\/node_modules\\/(@react-native|react-native|react-native-linear-gradient)\\/(.*)\\.js$",
"description": "The regexp pattern to match files containing the Flow type"
}
}
```
Expand Down
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
"ignoreChanges": [
"**/fixtures/**",
"**/__tests__/**",
"**/__snapshots__/**",
"**/test/**",
"**/*.md"
],
"npmClient": "yarn",
"useWorkspaces": true,
"conventionalCommits": true
}
23 changes: 11 additions & 12 deletions measure-bundle-size/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,17 @@ yarn add measure-bundle-size
```ts
import {measureIterable, measure, type MeasureResult} from 'measure-bundle-size'

type MeasureOptions = {
debug?: boolean
log?: (...args: any[]) => void
stats?: boolean | 'tree' | 'table'
workspaceFolder?: string
flowPattern?: RegExp
loader?: esbuild.BuildOptions['loader']
}

// Lazy async generator API
type measureIterable = (input: string, fileName?: string | null, {
debug?: boolean
log?: (...args: any[]) => void
stats?: boolean | 'tree' | 'table'
workspaceFolder?: string
}) => AsyncGenerator<MeasureResult>
type measureIterable = (input: string, fileName?: string | null, MeasureOptions) => AsyncGenerator<MeasureResult>

for await (const result of measureIterable(`code`, __filename, {
debug: true,
Expand All @@ -33,12 +37,7 @@ for await (const result of measureIterable(`code`, __filename, {
}

// Promise API
type measure = (input: string, fileName?: string | null, {
debug?: boolean
log?: (...args: any[]) => void
stats?: boolean | 'tree' | 'table'
workspaceFolder?: string
}) => Promise<MeasureResult[]>
type measure = (input: string, fileName?: string | null, MeasureOptions) => Promise<MeasureResult[]>

const results = await measure(`code`, __filename, {
debug: true,
Expand Down
5 changes: 4 additions & 1 deletion measure-bundle-size/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,23 @@
"@babel/traverse": "^7.23.6",
"bytes": "^3.1.0",
"debug": "^4.3.2",
"escalade": "^3.1.1"
"escalade": "^3.1.1",
"sucrase": "^3.35.0"
},
"peerDependencies": {
"esbuild": "^0"
},
"devDependencies": {
"@ambarli/alias": "^0.0.0",
"@react-native/assets": "^1.0.0",
"@types/bytes": "^3.1.1",
"@types/debug": "^4.1.7",
"@types/dedent": "^0.7.0",
"@types/lodash": "^4.14.176",
"@types/react": "^17.0.32",
"date-fns": "^2.17.0",
"dedent": "^0.7.0",
"lodash": "^4.17.21",
"react": "^17.0.2",
"react-inline-center": "^1.0.1",
"sanitize.css": "^13.0.0",
Expand Down
84 changes: 81 additions & 3 deletions measure-bundle-size/src/measure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,26 @@ const peerExternalPlugin: esbuild.Plugin = {
},
}

const stripFlowTypesPlugin = (
pattern = /\/node_modules\/(@react-native|react-native|react-native-linear-gradient)\/(.*)\.js$/
): esbuild.Plugin => {
return {
name: 'flow',
setup(build) {
build.onLoad({filter: pattern}, async (args) => {
const source = await fs.readFile(args.path, 'utf8')
const {stripFlowTypes} = await import('./stripFlowTypes')
const output = stripFlowTypes(source.toString(), args.path)
const contents = output ?? ''
return {
contents,
loader: 'jsx',
}
})
},
}
}

type StatsOpt = boolean | 'tree' | 'table'

type BundleResult = {
Expand Down Expand Up @@ -128,19 +148,29 @@ const pickPkgFields = (pkg: Pkg) => {
])
}

/**
* TODO: add more support for RN
* - add a platform/target option: `target: 'web' | 'node' | 'neutral' | 'react-native'`
* - add exports support: `conditions: ['react-native', 'import']`
* - use jsx loader for .js
*/
const bundle = async (
statement: string,
importInfo: ImportInfo,
{
baseDir,
projectPkgFile,
flowPattern,
loader,
stats: statsOpt = false,
cache: cacheOpt = false,
}: {
baseDir: string
projectPkgFile?: string | void
stats?: StatsOpt
cache?: boolean
flowPattern?: RegExp
loader?: esbuild.BuildOptions['loader']
}
): Promise<BundleResult> => {
const bundleMark = timeMark<'bundle' | 'zip' | 'analyze'>()
Expand All @@ -164,6 +194,7 @@ const bundle = async (
return {} as T
})

// TODO: use current package path as the cache key
const cacheKey =
cacheOpt && rootPkg !== undefined && genCacheKey(rootPkg, entryInput)
if (cacheOpt && cacheKey) {
Expand Down Expand Up @@ -196,9 +227,33 @@ const bundle = async (
},
outfile: '<bundle>.js',
absWorkingDir: workingDir,
plugins: [builtinExternalPlugin, peerExternalPlugin],
plugins: [
builtinExternalPlugin,
peerExternalPlugin,
stripFlowTypesPlugin(flowPattern),
],
resolveExtensions: [
// RN
'.ios.js',
'.android.js',
'.native.js',
// defaults to .jsx,.js,.tsx,.ts,.css,.json
'.jsx',
'.js',
'.tsx',
'.ts',
'.css',
'.json',
],
loader: {
'.node': 'binary',
// RN or web app may use static assets
'.jpeg': 'empty',
'.jpg': 'empty',
'.png': 'empty',
'.webp': 'empty',
'.gif': 'empty',
...loader,
},
external,
target: 'esnext',
Expand Down Expand Up @@ -300,6 +355,14 @@ type MeasureOptions = {
* the path of the workspace folder
*/
workspaceFolder?: string
/**
* the files pattern to strip flow types
*/
flowPattern?: RegExp
/**
* custom loader for esbuild
*/
loader?: esbuild.BuildOptions['loader']
}

/**
Expand All @@ -308,7 +371,15 @@ type MeasureOptions = {
export async function* measureIterable(
input: string,
fileName?: string | null,
{debug, log, stats, cache, workspaceFolder}: MeasureOptions = {}
{
debug,
log,
stats,
cache,
workspaceFolder,
flowPattern,
loader,
}: MeasureOptions = {}
): AsyncGenerator<MeasureResult> {
if (debug) {
logger.enabled = true
Expand All @@ -335,7 +406,14 @@ export async function* measureIterable(
parseMark.end('parse')
logger(parseMark.print())

const bundleOpts = {baseDir, projectPkgFile, stats, cache}
const bundleOpts = {
baseDir,
projectPkgFile,
stats,
cache,
flowPattern,
loader,
}
for (const importInfo of result.imports) {
const statement = input.substring(importInfo.start, importInfo.end)
// await new Promise((resolve) => setTimeout(resolve, 500))
Expand Down
20 changes: 20 additions & 0 deletions measure-bundle-size/src/stripFlowTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {transform} from 'sucrase'

/**
* Strip flow types from the given source code.
*
* Why sucrase is used instead of babel, bundle size of this package:
* - `sucrase`: 848kb -> 1.1mb
* - `@babel/core` (with `@babel/plugin-transform-flow-strip-types`): 848kb -> 5.7mb
*/
export function stripFlowTypes(source: string, filename?: string) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
const result = transform(source, {
transforms: ['flow', 'jsx', 'imports'],
jsxRuntime: 'automatic',
production: true,
filePath: filename,
})
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access
return result.code
}
Loading
Loading