Skip to content

Commit

Permalink
Improve cjs transformer (fix #118)
Browse files Browse the repository at this point in the history
  • Loading branch information
ije committed Sep 10, 2021
1 parent d469494 commit 4d139ef
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 38 deletions.
95 changes: 64 additions & 31 deletions server/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,14 +195,14 @@ func (task *buildTask) build(tracing *stringSet) (esm *ESM, pkgCSS bool, err err
func(args api.OnResolveArgs) (api.OnResolveResult, error) {
specifier := strings.TrimSuffix(args.Path, "/")

// resolve `?alias` parameter
// resolve `?alias` query
if len(task.alias) > 0 {
if name, ok := task.alias[specifier]; ok {
specifier = name
}
}

// support nodejs builtin modules like `node:path`
// resolve nodejs builtin modules like `node:path`
if strings.HasPrefix(specifier, "node:") {
specifier = strings.TrimPrefix(specifier, "node:")
}
Expand Down Expand Up @@ -260,7 +260,7 @@ func (task *buildTask) build(tracing *stringSet) (esm *ESM, pkgCSS bool, err err
return api.OnResolveResult{}, nil
}
external.Add(url)
return api.OnResolveResult{Path: "ESM_SH_EXTERNAL:" + url, External: true}, nil
return api.OnResolveResult{Path: "__ESM_SH_EXTERNAL:" + url, External: true}, nil
}
}
}
Expand All @@ -274,9 +274,11 @@ func (task *buildTask) build(tracing *stringSet) (esm *ESM, pkgCSS bool, err err
return api.OnResolveResult{}, nil
}

// external
// todo: bundles sub-modules of deps that are not in `exports`

// dynamic external
external.Add(specifier)
return api.OnResolveResult{Path: "ESM_SH_EXTERNAL:" + specifier, External: true}, nil
return api.OnResolveResult{Path: "__ESM_SH_EXTERNAL:" + specifier, External: true}, nil
},
)
},
Expand Down Expand Up @@ -502,20 +504,53 @@ esbuild:
}
}
if importPath == "" {
err = fmt.Errorf("Could not resolve \"%s\" (Imported by \"%s\")", name, task.pkg.name)
err = fmt.Errorf("Could not resolve \"%s\" (Imported by \"%s\")", name, task.pkg.name)
return
}
buffer := bytes.NewBuffer(nil)
identifier := identify(name)
slice := bytes.Split(outputContent, []byte(fmt.Sprintf("\"ESM_SH_EXTERNAL:%s\"", name)))
slice := bytes.Split(outputContent, []byte(fmt.Sprintf("\"__ESM_SH_EXTERNAL:%s\"", name)))
cjsContext := false
cjsImported := false
cjsImports := newStringSet()
for i, p := range slice {
if cjsContext {
var marked bool
p = bytes.TrimPrefix(p, []byte{')'})
if bytes.HasPrefix(p, []byte{'.'}) {
// right shift to strip the object `key`
shift := 0
for i, l := 1, len(p); i < l; i++ {
c := p[i]
if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '$' {
shift++
} else {
break
}
}
if shift > 0 {
cjsImports.Add(string(p[1 : shift+1]))
marked = true
p = p[1:]
}
}

if !marked {
if pkg, err := parsePkg(name); err == nil {
meta, err := initESM(task.wd, *pkg, task.deps, nodeEnv)
// if the dependency is an es module without `default` export, then use star import
if err == nil && meta.Module != "" && !meta.ExportDefault {
cjsImports.Add("*")
marked = true
}
}
}
if !marked {
cjsImports.Add("default")
}
}
cjsContext = bytes.HasSuffix(p, []byte{'('})
cjsContext = bytes.HasSuffix(p, []byte{'('}) && !bytes.HasSuffix(p, []byte("import("))
if cjsContext {
// left shift to strip the `require` ident generated by esbuild
shift := 0
for i := len(p) - 2; i >= 0; i-- {
c := p[i]
Expand All @@ -528,27 +563,6 @@ esbuild:
if shift > 0 {
p = p[0 : len(p)-(shift+1)]
}
if !cjsImported {
wrote := false
versionPrefx := fmt.Sprintf("/v%d/", VERSION)
if strings.HasPrefix(importPath, versionPrefx) {
pkg, err := parsePkg(strings.TrimPrefix(importPath, versionPrefx))
if err == nil {
// here the submodule should be always empty
pkg.submodule = ""
meta, err := initESM(task.wd, *pkg, task.deps, nodeEnv)
// if the dependency is an es module without `default` export, then import star
if err == nil && meta.Module != "" && !meta.ExportDefault {
fmt.Fprintf(buffer, `import * as __%s$ from "%s";%s`, identifier, importPath, eol)
wrote = true
}
}
}
if !wrote {
fmt.Fprintf(buffer, `import __%s$ from "%s";%s`, identifier, importPath, eol)
}
cjsImported = true
}
}
buffer.Write(p)
if i < len(slice)-1 {
Expand All @@ -559,7 +573,26 @@ esbuild:
}
}
}
outputContent = buffer.Bytes()

if cjsImports.Size() > 0 {
buf := bytes.NewBuffer(nil)
// todo: spread `?alias` and `?deps`
for _, name := range cjsImports.Values() {
switch name {
case "default":
fmt.Fprintf(buf, `import __%s$ from "%s";%s`, identifier, importPath, eol)
case "*":
fmt.Fprintf(buf, `import * as __%s$ from "%s";%s`, identifier, importPath, eol)
default:
fmt.Fprintf(buf, `import { %s as __%s$%s } from "%s";%s`, name, identifier, name, importPath, eol)
}
}
outputContent = make([]byte, buf.Len()+buffer.Len())
copy(outputContent, buf.Bytes())
copy(outputContent[buf.Len():], buffer.Bytes())
} else {
outputContent = buffer.Bytes()
}
}

// add nodejs/deno compatibility
Expand Down
11 changes: 4 additions & 7 deletions server/esm.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,10 @@ func initESM(wd string, pkg pkg, deps pkgSlice, nodeEnv string) (esm *ESM, err e
}
}
if !defined {
if esm.Main != "" {
esm.Main = path.Join(path.Dir(esm.Main), pkg.submodule)
} else {
esm.Main = "./" + pkg.submodule
}
if esm.Module != "" {
esm.Module = path.Join(path.Dir(esm.Module), pkg.submodule)
esm.Module = pkg.submodule
} else {
esm.Main = pkg.submodule
}
esm.Types = ""
esm.Typings = ""
Expand Down Expand Up @@ -233,7 +230,7 @@ func checkESM(wd string, packageName string, moduleSpecifier string) (resolveNam
if pass {
esm := ast.ExportsKind == js_ast.ExportsESM
if !esm {
err = errors.New("not module")
err = errors.New("not a module")
return
}
for name := range ast.NamedExports {
Expand Down

0 comments on commit 4d139ef

Please sign in to comment.