// Copyright 2016-2022, Pulumi Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Large providers like azure-native generate code that has a high // startup overhead in Node JS due to loading thousands of modules at // once. The code in this file is dedicated to support on-demand // (lazy) loading of modules at Node level to speed up program // startup. package nodejs import ( "encoding/json" "fmt" "io" "sort" "strings" "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" ) type lazyLoadGen struct{} func newLazyLoadGen() *lazyLoadGen { return &lazyLoadGen{} } // Generates TypeScript code to re-export a generated module. For // resources and functions this is optimized to use lazy loading. // Falls back to eager re-export for everything else. func (ll *lazyLoadGen) genReexport(w io.Writer, exp fileInfo, importPath string) { if exp.fileType == functionFileType { // optimize lazy-loading function modules ll.genFunctionReexport(w, exp.functionFileInfo, importPath) } else if exp.fileType == resourceFileType { // optimize lazy-loading resource modules ll.genResourceReexport(w, exp.resourceFileInfo, importPath) } else { // non-optimized but foolproof eager reexport fmt.Fprintf(w, "export * from %q;\n", importPath) } } // Used as a follow up to multiple genReexport calls to generate a // lazy-load polyfill to all the properties that need that. func (*lazyLoadGen) genLazyLoads(w io.Writer, importPath string, properties ...string) { sort.Strings(properties) j, err := json.Marshal(properties) contract.AssertNoErrorf(err, "error serializing properties") fmt.Fprintf(w, "utilities.lazyLoad(exports, %s, () => require(%q));\n", string(j), importPath) } // Generates TypeScript code that lazily imports and re-exports a // module defining a resource, while also importing the resoure class // in-scope. Needs to know which names the module defines // (resourceFileInfo). func (ll *lazyLoadGen) genResourceReexport(w io.Writer, i resourceFileInfo, importPath string) { defer fmt.Fprintf(w, "\n") quotedImport := fmt.Sprintf("%q", importPath) // not sure how to lazy-load in presence of re-exported // namespaces; bail and use an eager load in this case; also // eager-import the class. if i.methodsNamespaceName != "" { fmt.Fprintf(w, "export * from %s;\n", quotedImport) fmt.Fprintf(w, "import { %s } from %s;\n", i.resourceClassName, quotedImport) return } // Re-export interfaces. This is type-only and does not // generate a require() call. fmt.Fprintf(w, "export { %s } from %s;\n", strings.Join(i.interfaces(), ", "), quotedImport) // Re-export class type into the type group, see // https://www.typescriptlang.org/docs/handbook/declaration-merging.html fmt.Fprintf(w, "export type %[1]s = import(%[2]s).%[1]s;\n", i.resourceClassName, quotedImport) // Mock re-export class value into the value group - for compilation. fmt.Fprintf(w, "export const %[1]s: typeof import(%[2]s).%[1]s = null as any;\n", i.resourceClassName, quotedImport) ll.genLazyLoads(w, importPath, i.resourceClassName) } // Generates TypeScript code that lazily imports and re-exports a // module defining a function. Needs to which names the module defines // (functionFileInfo). func (ll *lazyLoadGen) genFunctionReexport(w io.Writer, i functionFileInfo, importPath string) { defer fmt.Fprintf(w, "\n") quotedImport := fmt.Sprintf("%q", importPath) // Re-export interfaces. This is type-only and does not // generate a require() call. interfaces := i.interfaces() if len(interfaces) > 0 { fmt.Fprintf(w, "export { %s } from %s;\n", strings.Join(interfaces, ", "), quotedImport) } // Re-export function values into the value group, and install lazy loading. funcs := i.functions() for _, f := range funcs { fmt.Fprintf(w, "export const %[1]s: typeof import(%[2]s).%[1]s = null as any;\n", f, quotedImport) } ll.genLazyLoads(w, importPath, funcs...) }