UNPKG

@adobe/helix-deploy

Version:

Library and Commandline Tools to build and deploy OpenWhisk Actions

194 lines (179 loc) 5.94 kB
/* * Copyright 2019 Adobe. All rights reserved. * This file is licensed to you 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 REPRESENTATIONS * OF ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ import { fileURLToPath } from 'url'; import path from 'path'; import fse from 'fs-extra'; import * as esbuild from 'esbuild'; import chalk from 'chalk-template'; import processQueue from '@adobe/helix-shared-process-queue'; import BaseBundler from './BaseBundler.js'; // eslint-disable-next-line no-underscore-dangle const __dirname = path.resolve(fileURLToPath(import.meta.url), '..'); /** * ESBuild based bundler */ export default class ESBuildBundler extends BaseBundler { /** * Create a new bundler. * * @param {import('../BaseConfig.js')} cfg base config */ constructor(cfg) { super(cfg); this.arch = 'node'; this.type = 'esbuild'; } async init() { if (!this.cfg.esm) { throw new Error('ESBuild bundler only supports ESM builds.'); } } async getESBuildConfig() { const { cfg } = this; /** @type {esbuild.BuildOptions} */ const opts = { format: 'esm', platform: 'node', bundle: true, outfile: path.relative(cfg.cwd, cfg.bundle), external: [ '@aws-sdk/*', // the following are imported by the universal adapter and are assumed to be available './params.json', 'aws-sdk', '@google-cloud/secret-manager', '@google-cloud/storage', ], banner: { js: [ 'import { createRequire as hedyCreateRequire } from "module";', 'const require = hedyCreateRequire(import.meta.url);', '', ].join('\n'), }, entryPoints: [ cfg.adapterFile || path.resolve(__dirname, '..', 'template', 'node-index.mjs'), ], absWorkingDir: cfg.cwd, plugins: [{ name: 'alias-main', setup: (build) => { build.onResolve({ filter: /^\.\/main\.js$/ }, () => ({ path: cfg.file })); build.onResolve( { filter: /^@adobe\/helix-universal$/ }, (args) => { const cwd = (process.env.HELIX_DEPLOY_USE_LOCAL === 'true') // for testing use "our" @adobe/helix-universal dependency ? path.resolve(__dirname, '..', '..') // for production use @adobe/helix-universal (and its dependencies) // in the calling service : cfg.cwd; return { path: path.resolve(cwd, 'node_modules', args.path, 'src', 'index.js') }; }, ); cfg.externals.forEach((external) => { build.onResolve({ filter: new RegExp(`^${external}$`) }, (args) => ({ path: args.path, external: true })); }); cfg.serverlessExternals.forEach((external) => { build.onResolve({ filter: new RegExp(`^${external}$`) }, (args) => ({ path: args.path, external: true })); }); }, }], metafile: true, }; if (cfg.minify) { opts.minify = cfg.minify; } if (cfg.progressHandler) { this.initProgressHandler(opts, cfg); } return opts; } async createESBuildBundle(arch) { const { cfg } = this; if (!cfg.depFile) { throw Error('dependencies info path is undefined'); } const m = cfg.minify ? 'minified ' : ''; if (!cfg.progressHandler) { cfg.log.info(`--: creating ${arch} ${m}bundle using esbuild ...`); } const config = await this.getESBuildConfig(); const result = await esbuild.build(config); await this.resolveDependencyInfos(result.metafile); // write dependencies info file await fse.writeJson(cfg.depFile, cfg.dependencies, { spaces: 2 }); if (!cfg.progressHandler) { cfg.log.info(chalk`{green ok:} created ${arch} bundle {yellow ${config.outfile}}`); } return result; } async createBundle() { if (!this.cfg.bundle) { throw Error('bundle path is undefined'); } return this.createESBuildBundle('node'); } /** * Resolves the dependencies by chunk. eg: * * { * 'src/idx_json.bundle.js': [{ * id: '@adobe/helix-epsagon:1.2.0', * name: '@adobe/helix-epsagon', * version: '1.2.0' }, * ], * ... * } */ async resolveDependencyInfos(metafile) { const { cfg } = this; // get list of dependencies const resolved = {}; const deps = {}; const depNames = [...Object.keys(metafile.inputs)]; await processQueue(depNames, async (depName) => { const absDepPath = path.resolve(cfg.cwd, depName); const segs = absDepPath.split('/'); let idx = segs.lastIndexOf('node_modules'); if (idx < 0) { return; } if (idx >= 0) { idx += 1; if (segs[idx].charAt(0) === '@') { idx += 1; } } segs.splice(idx + 1); const dir = path.resolve('/', ...segs); try { if (!resolved[dir]) { const pkgJson = await fse.readJson(path.resolve(dir, 'package.json')); const id = `${pkgJson.name}:${pkgJson.version}`; resolved[dir] = { id, name: pkgJson.name, version: pkgJson.version, }; } const dep = resolved[dir]; deps[dep.id] = dep; } catch (e) { // ignore } }); // sort the deps cfg.dependencies.main = Object.values(deps) .sort((d0, d1) => d0.name.localeCompare(d1.name)); } }