Skip to content

Commit

Permalink
Support import attributes (#976)
Browse files Browse the repository at this point in the history
  • Loading branch information
ije authored Jan 4, 2025
1 parent d07a83d commit e4f3d01
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 137 deletions.
142 changes: 76 additions & 66 deletions server/build.go

Large diffs are not rendered by default.

115 changes: 70 additions & 45 deletions server/build_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func (ctx *BuildContext) Path() string {
asteriskPrefix = "*"
}

esm := ctx.esmPath
esm := ctx.esm
if ctx.target == "types" {
if strings.HasSuffix(esm.SubPath, ".d.ts") {
ctx.path = fmt.Sprintf(
Expand Down Expand Up @@ -155,7 +155,7 @@ func (ctx *BuildContext) existsPkgFile(fp ...string) bool {
args := make([]string, 3+len(fp))
args[0] = ctx.wd
args[1] = "node_modules"
args[2] = ctx.esmPath.PkgName
args[2] = ctx.esm.PkgName
copy(args[3:], fp)
return existsFile(path.Join(args...))
}
Expand Down Expand Up @@ -323,7 +323,7 @@ func (ctx *BuildContext) resolveEntry(esmPath EsmPath) (entry BuildEntry) {

// check if the sub-module is a directory and has a package.json
var rawInfo PackageJSONRaw
if utils.ParseJSONFile(path.Join(ctx.wd, "node_modules", ctx.esmPath.PkgName, subModuleName, "package.json"), &rawInfo) == nil {
if utils.ParseJSONFile(path.Join(ctx.wd, "node_modules", ctx.esm.PkgName, subModuleName, "package.json"), &rawInfo) == nil {
p := rawInfo.ToNpmPackage()
if entry.main == "" {
if p.Module != "" {
Expand Down Expand Up @@ -582,7 +582,7 @@ func (ctx *BuildContext) finalizeBuildEntry(entry *BuildEntry) {
entry.main = ""
} else if !entry.module && !strings.HasSuffix(entry.main, ".cjs") {
// check if the cjs entry is an ESM
isESM, _, err := validateModuleFile(path.Join(ctx.wd, "node_modules", ctx.esmPath.PkgName, entry.main))
isESM, _, err := validateModuleFile(path.Join(ctx.wd, "node_modules", ctx.esm.PkgName, entry.main))
if err == nil {
entry.module = isESM
}
Expand Down Expand Up @@ -669,7 +669,7 @@ func (ctx *BuildContext) resolveConditionExportEntry(conditions JSONObject, pref
} else if ctx.isDenoTarget() {
conditionName := "deno"
// [workaround] to support ssr in Deno, use `node` condition for solid-js < 1.6.0
if ctx.esmPath.PkgName == "solid-js" && semverLessThan(ctx.esmPath.PkgVersion, "1.6.0") {
if ctx.esm.PkgName == "solid-js" && semverLessThan(ctx.esm.PkgVersion, "1.6.0") {
conditionName = "node"
}
conditionFound = applyCondition(conditionName)
Expand Down Expand Up @@ -746,14 +746,14 @@ LOOP:
return
}

func (ctx *BuildContext) resolveExternalModule(specifier string, kind api.ResolveKind, analyzeMode bool) (resolvedPath string, err error) {
func (ctx *BuildContext) resolveExternalModule(specifier string, kind api.ResolveKind, withTypeJSON bool, analyzeMode bool) (resolvedPath string, err error) {
// return the specifier directly in analyze mode
if analyzeMode {
return specifier, nil
}

defer func() {
if err == nil {
if err == nil && !withTypeJSON {
resolvedPathFull := resolvedPath
// use relative path for sub-module of current package
if strings.HasPrefix(specifier, ctx.pkgJson.Name+"/") {
Expand All @@ -774,17 +774,18 @@ func (ctx *BuildContext) resolveExternalModule(specifier string, kind api.Resolv
}
}()

// it's the entry of current package from GitHub
if pkgJson := ctx.pkgJson; ctx.esmPath.GhPrefix && (specifier == pkgJson.Name || specifier == pkgJson.PkgName) {
// if it's the main entry of current package
if pkgJson := ctx.pkgJson; specifier == pkgJson.Name || specifier == pkgJson.PkgName {
resolvedPath = ctx.getImportPath(EsmPath{
PkgName: pkgJson.Name,
PkgVersion: pkgJson.Version,
GhPrefix: true,
GhPrefix: ctx.esm.GhPrefix,
PrPrefix: ctx.esm.PrPrefix,
}, ctx.getBuildArgsPrefix(false), ctx.externalAll)
return
}

// node builtin module
// if it's a node builtin module
if isNodeBuiltInModule(specifier) {
if ctx.externalAll || ctx.target == "node" || ctx.target == "denonext" || ctx.args.external.Has(specifier) {
resolvedPath = specifier
Expand All @@ -802,30 +803,40 @@ func (ctx *BuildContext) resolveExternalModule(specifier string, kind api.Resolv
return
}

// it's a sub-module of current package
// if it's a sub-module of current package
if strings.HasPrefix(specifier, ctx.pkgJson.Name+"/") {
subPath := strings.TrimPrefix(specifier, ctx.pkgJson.Name+"/")
subModule := EsmPath{
GhPrefix: ctx.esmPath.GhPrefix,
PrPrefix: ctx.esmPath.PrPrefix,
PkgName: ctx.esmPath.PkgName,
PkgVersion: ctx.esmPath.PkgVersion,
GhPrefix: ctx.esm.GhPrefix,
PrPrefix: ctx.esm.PrPrefix,
PkgName: ctx.esm.PkgName,
PkgVersion: ctx.esm.PkgVersion,
SubPath: subPath,
SubModuleName: stripEntryModuleExt(subPath),
}
resolvedPath = ctx.getImportPath(subModule, ctx.getBuildArgsPrefix(false), ctx.externalAll)
if ctx.bundleMode == BundleFalse {
n, e := utils.SplitByLastByte(resolvedPath, '.')
resolvedPath = n + ".nobundle." + e
if withTypeJSON {
resolvedPath = "/" + subModule.Specifier()
if !strings.HasSuffix(subPath, ".json") {
entry := ctx.resolveEntry(subModule)
if entry.main != "" {
resolvedPath = "/" + subModule.Name() + entry.main[1:]
}
}
} else {
resolvedPath = ctx.getImportPath(subModule, ctx.getBuildArgsPrefix(false), ctx.externalAll)
if ctx.bundleMode == BundleFalse {
n, e := utils.SplitByLastByte(resolvedPath, '.')
resolvedPath = n + ".nobundle." + e
}
}
return
}

// common npm dependency
pkgName, version, subpath, _ := splitEsmPath(specifier)
pkgName, version, subPath, _ := splitEsmPath(specifier)
if version == "" {
if pkgName == ctx.esmPath.PkgName {
version = ctx.esmPath.PkgVersion
if pkgName == ctx.esm.PkgName {
version = ctx.esm.PkgVersion
} else if pkgVerson, ok := ctx.args.deps[pkgName]; ok {
version = pkgVerson
} else if v, ok := ctx.pkgJson.Dependencies[pkgName]; ok {
Expand All @@ -837,16 +848,11 @@ func (ctx *BuildContext) resolveExternalModule(specifier string, kind api.Resolv
}
}

// force the version of 'react' (as dependency) equals to 'react-dom'
// if ctx.esmPath.PkgName == "react-dom" && pkgName == "react" {
// version = ctx.esmPath.PkgVersion
// }

dep := EsmPath{
PkgName: pkgName,
PkgVersion: version,
SubPath: subpath,
SubModuleName: stripEntryModuleExt(subpath),
SubPath: subPath,
SubModuleName: stripEntryModuleExt(subPath),
}

// resolve alias in dependencies
Expand All @@ -855,7 +861,7 @@ func (ctx *BuildContext) resolveExternalModule(specifier string, kind api.Resolv
// e.g. "react": "github:facebook/react#v18.2.0"
p, err := resolveDependencyVersion(version)
if err != nil {
resolvedPath = fmt.Sprintf("/error.js?type=%s&name=%s&importer=%s", strings.ReplaceAll(err.Error(), " ", "-"), pkgName, ctx.esmPath.Specifier())
resolvedPath = fmt.Sprintf("/error.js?type=%s&name=%s&importer=%s", strings.ReplaceAll(err.Error(), " ", "-"), pkgName, ctx.esm.Specifier())
return
}
if p.Name != "" {
Expand All @@ -880,26 +886,45 @@ func (ctx *BuildContext) resolveExternalModule(specifier string, kind api.Resolv
}
}

var isFixedVersion bool
if dep.GhPrefix {
isFixedVersion = isCommitish(dep.PkgVersion) || regexpVersionStrict.MatchString(strings.TrimPrefix(dep.PkgVersion, "v"))
} else if dep.PrPrefix {
isFixedVersion = true
} else {
isFixedVersion = regexpVersionStrict.MatchString(dep.PkgVersion)
if withTypeJSON {
resolvedPath = "/" + dep.Specifier()
if subPath == "" || !strings.HasSuffix(subPath, ".json") {
b := &BuildContext{
esm: dep,
npmrc: ctx.npmrc,
zoneId: ctx.zoneId,
}
err = b.install()
if err != nil {
return
}
entry := b.resolveEntry(dep)
if entry.main != "" {
resolvedPath = "/" + dep.Name() + entry.main[1:]
}
}
return
}

args := BuildArgs{
alias: ctx.args.alias,
deps: ctx.args.deps,
external: ctx.args.external,
conditions: ctx.args.conditions,
}

err = resolveBuildArgs(ctx.npmrc, ctx.wd, &args, dep)
if err != nil {
return
}

var isFixedVersion bool
if dep.GhPrefix {
isFixedVersion = isCommitish(dep.PkgVersion) || regexpVersionStrict.MatchString(strings.TrimPrefix(dep.PkgVersion, "v"))
} else if dep.PrPrefix {
isFixedVersion = true
} else {
isFixedVersion = regexpVersionStrict.MatchString(dep.PkgVersion)
}
if isFixedVersion {
buildArgsPrefix := ""
if a := encodeBuildArgs(args, false); a != "" {
Expand Down Expand Up @@ -973,13 +998,13 @@ func (ctx *BuildContext) resloveDTS(entry BuildEntry) (string, error) {
}
return fmt.Sprintf(
"/%s/%s%s",
ctx.esmPath.Name(),
ctx.esm.Name(),
ctx.getBuildArgsPrefix(true),
strings.TrimPrefix(entry.types, "./"),
), nil
}

if ctx.esmPath.SubPath != "" && (ctx.pkgJson.Types != "" || ctx.pkgJson.Typings != "") {
if ctx.esm.SubPath != "" && (ctx.pkgJson.Types != "" || ctx.pkgJson.Typings != "") {
return "", nil
}

Expand All @@ -1002,11 +1027,11 @@ func (ctx *BuildContext) resloveDTS(entry BuildEntry) (string, error) {
dtsModule := EsmPath{
PkgName: typesPkgName,
PkgVersion: p.Version,
SubPath: ctx.esmPath.SubPath,
SubModuleName: ctx.esmPath.SubModuleName,
SubPath: ctx.esm.SubPath,
SubModuleName: ctx.esm.SubModuleName,
}
b := &BuildContext{
esmPath: dtsModule,
esm: dtsModule,
npmrc: ctx.npmrc,
args: ctx.args,
externalAll: ctx.externalAll,
Expand Down Expand Up @@ -1046,7 +1071,7 @@ func (ctx *BuildContext) lexer(entry *BuildEntry) (ret *BuildMeta, cjsExports []

var isESM bool
var namedExports []string
isESM, namedExports, err = validateModuleFile(path.Join(ctx.wd, "node_modules", ctx.esmPath.PkgName, entry.main))
isESM, namedExports, err = validateModuleFile(path.Join(ctx.wd, "node_modules", ctx.esm.PkgName, entry.main))
if err != nil {
return
}
Expand Down
6 changes: 3 additions & 3 deletions server/build_rewriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ var (
)

func (ctx *BuildContext) rewriteJS(in []byte) (out []byte, dropSourceMap bool) {
switch ctx.esmPath.PkgName {
switch ctx.esm.PkgName {
case "axios", "cross-fetch", "whatwg-fetch":
if ctx.isDenoTarget() {
xhr := []byte("\nimport \"https://deno.land/x/[email protected]/mod.ts\";")
Expand All @@ -31,7 +31,7 @@ func (ctx *BuildContext) rewriteJS(in []byte) (out []byte, dropSourceMap bool) {
}

case "iconv-lite":
if ctx.isDenoTarget() && semverLessThan(ctx.esmPath.PkgVersion, "0.5.0") {
if ctx.isDenoTarget() && semverLessThan(ctx.esm.PkgVersion, "0.5.0") {
old := "__Process$.versions.node"
new := "__Process$.versions.nope"
return bytes.Replace(in, []byte(old), []byte(new), 1), false
Expand All @@ -41,7 +41,7 @@ func (ctx *BuildContext) rewriteJS(in []byte) (out []byte, dropSourceMap bool) {
}

func (ctx *BuildContext) rewriteDTS(filename string, dts []byte) []byte {
switch ctx.esmPath.PkgName {
switch ctx.esm.PkgName {
case "preact":
// fix preact/compat types
if filename == "./compat/src/index.d.ts" {
Expand Down
8 changes: 4 additions & 4 deletions server/cjs_module_lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func cjsModuleLexer(ctx *BuildContext, cjsEntry string) (ret cjsModuleLexerResul
defer func() {
if err == nil {
if DEBUG {
log.Debugf("[cjsModuleLexer] parse %s in %s", path.Join(ctx.esmPath.PkgName, cjsEntry), time.Since(start))
log.Debugf("[cjsModuleLexer] parse %s in %s", path.Join(ctx.esm.PkgName, cjsEntry), time.Since(start))
}
if !existsFile(cacheFileName) {
ensureDir(path.Dir(cacheFileName))
Expand All @@ -70,9 +70,9 @@ func cjsModuleLexer(ctx *BuildContext, cjsEntry string) (ret cjsModuleLexerResul
}
}()

if cjsModuleLexerIgnoredPackages.Has(ctx.esmPath.PkgName) {
if cjsModuleLexerIgnoredPackages.Has(ctx.esm.PkgName) {
js := path.Join(ctx.wd, "reveal_"+strings.ReplaceAll(cjsEntry[2:], "/", "_"))
err = os.WriteFile(js, []byte(fmt.Sprintf(`console.log(JSON.stringify(Object.keys((await import("npm:%s")).default)))`, path.Join(ctx.esmPath.Name(), cjsEntry))), 0644)
err = os.WriteFile(js, []byte(fmt.Sprintf(`console.log(JSON.stringify(Object.keys((await import("npm:%s")).default)))`, path.Join(ctx.esm.Name(), cjsEntry))), 0644)
if err != nil {
return
}
Expand Down Expand Up @@ -100,7 +100,7 @@ RETRY:
c, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

cmd := exec.CommandContext(c, "cjs-module-lexer", path.Join(ctx.esmPath.PkgName, cjsEntry))
cmd := exec.CommandContext(c, "cjs-module-lexer", path.Join(ctx.esm.PkgName, cjsEntry))
stdout, recycle := NewBuffer()
defer recycle()
stderr, recycle := NewBuffer()
Expand Down
24 changes: 12 additions & 12 deletions server/dts_transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func transformDTS(ctx *BuildContext, dts string, buildArgsPrefix string, marker
if isEntry {
marker = set.New[string]()
}
dtsPath := path.Join("/"+ctx.esmPath.Name(), buildArgsPrefix, dts)
dtsPath := path.Join("/"+ctx.esm.Name(), buildArgsPrefix, dts)
if marker.Has(dtsPath) {
// don't transform repeatly
return
Expand All @@ -46,7 +46,7 @@ func transformDTS(ctx *BuildContext, dts string, buildArgsPrefix string, marker
return
}

dtsFilePath := path.Join(ctx.wd, "node_modules", ctx.esmPath.PkgName, dts)
dtsFilePath := path.Join(ctx.wd, "node_modules", ctx.esm.PkgName, dts)
dtsWd := path.Dir(dtsFilePath)
dtsFile, err := os.Open(dtsFilePath)
if err != nil {
Expand All @@ -68,7 +68,7 @@ func transformDTS(ctx *BuildContext, dts string, buildArgsPrefix string, marker
internalDts := set.New[string]()

err = parseDts(dtsFile, buffer, func(specifier string, kind TsImportKind, position int) (string, error) {
if ctx.esmPath.PkgName == "@types/node" {
if ctx.esm.PkgName == "@types/node" {
if strings.HasPrefix(specifier, "node:") || nodeBuiltinModules[specifier] || isRelPathSpecifier(specifier) {
return specifier, nil
}
Expand Down Expand Up @@ -135,25 +135,25 @@ func transformDTS(ctx *BuildContext, dts string, buildArgsPrefix string, marker
specifier += "/" + subPath
}

if depPkgName == ctx.esmPath.PkgName {
if depPkgName == ctx.esm.PkgName {
if strings.ContainsRune(subPath, '*') {
return fmt.Sprintf(
"{ESM_CDN_ORIGIN}/%s/%s%s",
ctx.esmPath.Name(),
ctx.esm.Name(),
ctx.getBuildArgsPrefix(true),
subPath,
), nil
} else {
entry := ctx.resolveEntry(EsmPath{
PkgName: depPkgName,
PkgVersion: ctx.esmPath.PkgVersion,
PkgVersion: ctx.esm.PkgVersion,
SubPath: subPath,
SubModuleName: subPath,
})
if entry.types != "" {
return fmt.Sprintf(
"{ESM_CDN_ORIGIN}/%s/%s%s",
ctx.esmPath.Name(),
ctx.esm.Name(),
ctx.getBuildArgsPrefix(true),
strings.TrimPrefix(entry.types, "./"),
), nil
Expand Down Expand Up @@ -208,11 +208,11 @@ func transformDTS(ctx *BuildContext, dts string, buildArgsPrefix string, marker
}
args := BuildArgs{}
b := &BuildContext{
esmPath: dtsModule,
npmrc: ctx.npmrc,
args: args,
target: "types",
zoneId: ctx.zoneId,
esm: dtsModule,
npmrc: ctx.npmrc,
args: args,
target: "types",
zoneId: ctx.zoneId,
}
err = b.install()
if err != nil {
Expand Down
Loading

0 comments on commit e4f3d01

Please sign in to comment.