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: support package exports and custom paths #52

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ server({ port: PORT }, [
})
}
}),
get('/export-sizes', async ctx => {
get('/exports-sizes', async ctx => {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what's going on here, this is the path the frontend is using.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only a dev server — the frontend uses its own routes and server. But makes sense to synchronize here.

const packageString = decodeURIComponent(ctx.query.p)

try {
Expand Down
17 changes: 9 additions & 8 deletions src/getPackageExportSizes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,15 @@ export async function getAllPackageExports(
options: InstallPackageOptions = {}
) {
const startTime = performance.now()
const { name: packageName, normalPath } = parsePackageString(packageString)
const { name: packageName, normalPath, importPath, isLocal } = parsePackageString(packageString)
const installPath = await InstallationUtils.preparePath(packageName)

try {
await installPackage(packageString, installPath, options)
await installPackage(normalPath, installPath, options)
const results = await getAllExports(
packageString,
normalPath || installPath,
packageName
isLocal ? normalPath : installPath,
importPath
)
Telemetry.packageExports(packageString, startTime, true)
return results
Expand All @@ -57,16 +57,16 @@ export async function getPackageExportSizes(
}
) {
const startTime = performance.now()
const { name: packageName, normalPath } = parsePackageString(packageString)
const { name: packageName, normalPath, importPath, isLocal } = parsePackageString(packageString)
const installPath = await InstallationUtils.preparePath(packageName)

try {
await installPackage(packageString, installPath, options)
await installPackage(normalPath, installPath, options)

const exportMap = await getAllExports(
packageString,
normalPath || installPath,
packageName
isLocal ? normalPath : installPath,
importPath
)

const exports = Object.keys(exportMap).filter(exp => !(exp === 'default'))
Expand All @@ -77,6 +77,7 @@ export async function getPackageExportSizes(
const builtDetails = await BuildUtils.buildPackageIgnoringMissingDeps({
name: packageName,
installPath,
importPath,
externals,
options: {
customImports: exports,
Expand Down
34 changes: 21 additions & 13 deletions src/getPackageStats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,14 @@ export default async function getPackageStats(
...optionsRaw,
}

const { name: packageName, isLocal } = parsePackageString(packageString)
const { name: packageName, isLocal, normalPath, importPath } = parsePackageString(packageString)
const installPath = await InstallationUtils.preparePath(packageName)

if (options.debug) {
console.log('Install path:', installPath)
}
try {
await InstallationUtils.installPackage(packageString, installPath, {
await InstallationUtils.installPackage(normalPath, installPath, {
isLocal,
client: options.client,
limitConcurrency: options.limitConcurrency,
Expand All @@ -76,10 +76,11 @@ export default async function getPackageStats(
})

const externals = getExternals(packageName, installPath)
const [pacakgeJSONDetails, builtDetails] = await Promise.all([
const [packageJSONDetails, builtDetails] = await Promise.all([
getPackageJSONDetails(packageName, installPath),
BuildUtils.buildPackageIgnoringMissingDeps({
name: packageName,
importPath,
installPath,
externals,
options: {
Expand All @@ -91,30 +92,37 @@ export default async function getPackageStats(
}),
])

const hasCSSAsset = builtDetails.assets.some(asset => asset.type === 'css')
const mainAsset = builtDetails.assets.find(
asset =>
asset.name === 'main' && asset.type === (hasCSSAsset ? 'css' : 'js')
)
const mainAssets = builtDetails.assets.filter(asset => asset.name === 'main')

if (!mainAsset) {
if (!mainAssets.length) {
throw new UnexpectedBuildError(
'Did not find a main asset in the built bundle'
)
}

const mainSizes = mainAssets.reduce((acc, asset) => {
acc.size += asset.size
acc.gzip += asset.gzip
if (asset.parse) {
acc.parse = acc.parse || {baseParseTime: 0, scriptParseTime: 0}
acc.parse.baseParseTime += asset.parse.baseParseTime || 0
acc.parse.scriptParseTime += asset.parse.scriptParseTime || 0
}
return acc
}, { size: 0, gzip: 0, parse: null as null | { baseParseTime: number, scriptParseTime: number } })
Comment on lines +103 to +112
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I maintain a component library (Vuetify) that imports its own stylesheets. The way this was before would only include the size of the CSS instead of CSS + JS.


Telemetry.packageStats(
packageString,
true,
performance.now() - startTime,
options
)
return {
...pacakgeJSONDetails,
...packageJSONDetails,
...builtDetails,
size: mainAsset.size,
gzip: mainAsset.gzip,
parse: mainAsset.parse,
size: mainSizes.size,
gzip: mainSizes.gzip,
parse: mainSizes.parse,
}
} catch (e) {
Telemetry.packageStats(
Expand Down
23 changes: 16 additions & 7 deletions src/utils/build.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
CreateEntryPointOptions,
} from '../common.types'
import Telemetry from './telemetry.utils'
import { resolve } from './exports.utils'

type CompilePackageArgs = {
name: string
Expand All @@ -43,6 +44,7 @@ type CompilePackageReturn = {
type BuildPackageArgs = {
name: string
installPath: string
importPath: string,
externals: Externals
options: BuildPackageOptions
}
Expand All @@ -51,7 +53,7 @@ type WebpackStatsAsset = NonNullable<webpack.Stats.ToJsonOutput['assets']>[0]

const BuildUtils = {
createEntryPoint(
packageName: string,
importPath: string,
installPath: string,
options: CreateEntryPointOptions
) {
Expand All @@ -65,23 +67,24 @@ const BuildUtils = {
if (options.esm) {
if (options.customImports) {
importStatement = `
import { ${options.customImports.join(', ')} } from '${packageName}';
import { ${options.customImports.join(', ')} } from '${importPath}';
console.log(${options.customImports.join(', ')})
`
} else {
importStatement = `import p from '${packageName}'; console.log(p)`
importStatement = `import p from '${importPath}'; console.log(p)`
}
} else {
if (options.customImports) {
importStatement = `
const { ${options.customImports.join(
', '
)} } = require('${packageName}');
)} } = await import(/* webpackMode: 'eager' */'${importPath}');
console.log(${options.customImports.join(', ')})
`
} else {
importStatement = `const p = require('${packageName}'); console.log(p)`
importStatement = `const p = await import(/* webpackMode: 'eager' */'${importPath}'); console.log(p)`
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.mjs can't be .require()d

}
importStatement = `;(async () => { ${importStatement} })();`
}

try {
Expand Down Expand Up @@ -177,6 +180,7 @@ const BuildUtils = {
async buildPackage({
name,
installPath,
importPath,
externals,
options,
}: BuildPackageArgs) {
Expand All @@ -187,14 +191,14 @@ const BuildUtils = {
return { assets: [] }
}
options.customImports.forEach(importt => {
entry[importt] = BuildUtils.createEntryPoint(name, installPath, {
entry[importt] = BuildUtils.createEntryPoint(importPath, installPath, {
customImports: [importt],
entryFilename: importt,
esm: true,
})
})
} else {
entry['main'] = BuildUtils.createEntryPoint(name, installPath, {
entry['main'] = BuildUtils.createEntryPoint(importPath, installPath, {
esm: false,
customImports: options.customImports,
})
Expand Down Expand Up @@ -330,16 +334,20 @@ const BuildUtils = {
name,
externals,
installPath,
importPath,
options,
}: BuildPackageArgs) {
const buildStartTime = performance.now()
let buildIteration = 1

importPath = await resolve(installPath, importPath)
Copy link
Author

@KaelWD KaelWD Aug 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're still using webpack 4, but have webpack 5's enhanced-resolve. This resolves imports to an absolute path so webpack 4 can load them.


try {
const buildResult = await BuildUtils.buildPackage({
name,
externals,
installPath,
importPath,
options,
})
Telemetry.buildPackage(name, true, buildStartTime, {
Expand Down Expand Up @@ -367,6 +375,7 @@ const BuildUtils = {
const rebuiltResult = await BuildUtils.buildPackage({
name,
externals: newExternals,
importPath,
installPath,
options,
})
Expand Down
58 changes: 45 additions & 13 deletions src/utils/common.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import path from 'path'
import builtInModules from 'builtin-modules'
import fs from 'fs'
import os from 'os'
import { last } from 'lodash'

const homeDirectory = os.homedir()

Expand Down Expand Up @@ -92,8 +93,9 @@ type ParsePackageResult = {
name: string
version: string | null
scoped: boolean
importPath: string
isLocal?: boolean
normalPath?: string
normalPath: string
}

function parseLocalPackageString(packageString: string): ParsePackageResult {
Expand All @@ -104,33 +106,63 @@ function parseLocalPackageString(packageString: string): ParsePackageResult {
name: packageJSON.name,
version: packageJSON.version,
scoped: packageJSON.name.startsWith('@'),
importPath: packageString,
normalPath: packageString,
isLocal: true,
}
}

function parseScopedPackageString(packageString: string): ParsePackageResult {
const lastAtIndex = packageString.lastIndexOf('@')
const firstSlashIndex = packageString.indexOf('/')
const secondSlashIndex = packageString.indexOf('/', firstSlashIndex + 1)

const name = lastAtIndex === 0
? secondSlashIndex === -1
? packageString
: packageString.substring(0, secondSlashIndex)
: packageString.substring(0, lastAtIndex)
const version = lastAtIndex === 0
? null
: secondSlashIndex === -1
? packageString.substring(lastAtIndex + 1)
: packageString.substring(lastAtIndex + 1, secondSlashIndex)
const path = secondSlashIndex === -1
? null
: packageString.substring(secondSlashIndex + 1)

return {
name:
lastAtIndex === 0
? packageString
: packageString.substring(0, lastAtIndex),
version:
lastAtIndex === 0 ? null : packageString.substring(lastAtIndex + 1),
name,
importPath: name + (path ? '/' + path : ''),
version,
normalPath: name + (version ? '@' + version : ''),
scoped: true,
}
}

function parseUnscopedPackageString(packageString: string): ParsePackageResult {
const lastAtIndex = packageString.lastIndexOf('@')
const firstSlashIndex = packageString.indexOf('/')

const name = lastAtIndex === -1
? firstSlashIndex === -1
? packageString
: packageString.substring(0, firstSlashIndex)
: packageString.substring(0, lastAtIndex)
const version = lastAtIndex === -1
? null
: firstSlashIndex === -1
? packageString.substring(lastAtIndex + 1)
: packageString.substring(lastAtIndex + 1, firstSlashIndex)
const path = firstSlashIndex === -1
? null
: packageString.substring(firstSlashIndex + 1)

return {
name:
lastAtIndex === -1
? packageString
: packageString.substring(0, lastAtIndex),
version:
lastAtIndex === -1 ? null : packageString.substring(lastAtIndex + 1),
name,
importPath: name + (path ? '/' + path : ''),
version,
normalPath: name + (version ? '@' + version : ''),
scoped: false,
}
}
Expand Down
6 changes: 4 additions & 2 deletions src/utils/exports.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,10 +223,11 @@ const resolver = enhancedResolve.create({
modules: webpackConfig?.resolve?.modules,
// @ts-ignore Error due to unsynced types for enhanced resolve and webpack
mainFields: webpackConfig?.resolve?.mainFields,
conditionNames: ['import', 'require', 'node', 'browser', 'default']
})

const resolve = async (context: string, path: string): Promise<string> =>
new Promise((resolve, reject) => {
export function resolve (context: string, path: string): Promise<string> {
return new Promise((resolve, reject) => {
resolver(context, path, (err: Error, result: string) => {
if (err) {
reject(err)
Expand All @@ -235,6 +236,7 @@ const resolve = async (context: string, path: string): Promise<string> =>
}
})
})
}

type ResolvedExports = {
[key: string]: string
Expand Down