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): Add build command by wrapping rollup #2544

Merged
merged 10 commits into from
Oct 24, 2023
2 changes: 1 addition & 1 deletion chompfile.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ deps = ['dist/cli.js', 'docs.md']
[[task]]
target = 'dist/cli.js'
deps = ['src/**/*.ts', 'npm:install']
run = 'esbuild src/cli.ts --bundle --platform=node --external:@jspm/generator --external:ora --external:picocolors --external:@babel/core --format=esm --outfile=$TARGET'
run = 'esbuild src/cli.ts --bundle --platform=node --external:fsevents --external:@jspm/generator --external:ora --external:picocolors --external:@babel/core --format=esm --outfile=$TARGET'
JayaKrishnaNamburu marked this conversation as resolved.
Show resolved Hide resolved

[[task]]
name = 'docs'
Expand Down
19 changes: 17 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"@jspm/generator": "^1.1.10",
"cac": "^6.7.14",
"ora": "^6.3.0",
"picocolors": "^1.0.0"
"picocolors": "^1.0.0",
"rollup": "^3.29.2"
},
"devDependencies": {
"@antfu/eslint-config": "^0.34.2",
Expand Down
60 changes: 60 additions & 0 deletions src/build/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import path from "node:path";
import process from "node:process";
import fs from "node:fs/promises";
import { type RollupOptions, rollup } from "rollup";

import { JspmError, exists } from "../utils";
import type { Flags } from "../types";
import { RollupImportmapPlugin } from "./rollup-importmap-plugin";

export default async function build(flags: Flags) {
if (!flags.entry && !flags.buildConfig) {
throw new JspmError(`Please provide entry for the build`);
}

let buildConfig: RollupOptions;
let outputOptions: RollupOptions["output"];

if (flags.entry) {
JayaKrishnaNamburu marked this conversation as resolved.
Show resolved Hide resolved
const entryPath = path.join(process.cwd(), flags.entry);
if ((await exists(entryPath)) === false) {
throw new JspmError(`Entry file does not exist: ${entryPath}`);
}
buildConfig = {
input: entryPath,
plugins: [RollupImportmapPlugin(flags)],
};
}

if (flags.buildConfig) {
const buildConfigPath = path.join(process.cwd(), flags.buildConfig);
if ((await exists(buildConfigPath)) === false) {
throw new JspmError(
`Build config file does not exist: ${buildConfigPath}`
);
}
const rollupConfig = await import(buildConfigPath)
.then((mod) => mod.default)
.catch((err) => {
throw new JspmError(`Failed to load build config: ${err}`);
});

if ("output" in rollupConfig) {
outputOptions = rollupConfig.output;
}

buildConfig = {
...rollupConfig,
plugins: [...(rollupConfig?.plugins || []), RollupImportmapPlugin(flags)],
};
}

const builder = await rollup(buildConfig);
const result = await builder.generate({ format: "esm", ...outputOptions });

for (const file of result.output) {
const outputPath = path.join(process.cwd(), file.fileName);
const content = file.type === "asset" ? file.source : file.code;
await fs.writeFile(outputPath, content, "utf-8");
}
}
51 changes: 51 additions & 0 deletions src/build/rollup-importmap-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Plugin } from "rollup";
import fs from "node:fs/promises";
import path from "node:path";
import { Flags } from "../types";
import { getGenerator, JspmError } from "../utils";

export const RollupImportmapPlugin = async (flags: Flags): Promise<Plugin> => {
const generator = await getGenerator({ ...flags, freeze: true });
await generator.install();

return {
name: "rollup-importmap-plugin",
resolveId: async (id: string, importer: string) => {
if (importer?.startsWith("http") && id?.startsWith(".")) {
const proxyPath = new URL(id, importer).toString();
return { id: proxyPath, external: false };
}
JayaKrishnaNamburu marked this conversation as resolved.
Show resolved Hide resolved

try {
const resolved = generator.importMap.resolve(id, importer);
return { id: resolved };
JayaKrishnaNamburu marked this conversation as resolved.
Show resolved Hide resolved
} catch (err) {
return { id, external: true };
JayaKrishnaNamburu marked this conversation as resolved.
Show resolved Hide resolved
}
},
load: async (id: string) => {
try {
const url = new URL(id);

if (url.protocol === "file:") {
/*
This is a hack and need to be replaced with a proper solution.
*/
JayaKrishnaNamburu marked this conversation as resolved.
Show resolved Hide resolved
const filePath =
path.extname(url.pathname) === ""
? `${url.pathname}.js`
: url.pathname;
JayaKrishnaNamburu marked this conversation as resolved.
Show resolved Hide resolved

return await fs.readFile(filePath, "utf-8");
}

if (url.protocol === "https:") {
const response = await fetch(id);
return await response.text();
}
} catch (err) {
throw new JspmError(`\n Failed to resolve ${id}: ${err.message} \n`);
JayaKrishnaNamburu marked this conversation as resolved.
Show resolved Hide resolved
}
},
};
};
23 changes: 21 additions & 2 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import install from "./install";
import link from "./link";
import uninstall from "./uninstall";
import update from "./update";
import { JspmError, availableProviders , wrapCommand } from "./utils";
import { JspmError, availableProviders, wrapCommand } from "./utils";
import build from "./build/index";

export const cli = cac(c.yellow("jspm"));

Expand All @@ -44,7 +45,9 @@ const resolutionOpt: opt = [
];
const providerOpt: opt = [
"-p, --provider <provider>",
`Default module provider. Available providers: ${availableProviders.join(", ")}`,
`Default module provider. Available providers: ${availableProviders.join(
", "
)}`,
{},
];
const stdoutOpt: opt = [
Expand Down Expand Up @@ -88,6 +91,16 @@ const freezeOpt: opt = [
{ default: false },
];
const silentOpt: opt = ["--silent", "Silence all output", { default: false }];
const buildOpt: opt = [
"--entry <file>",
JayaKrishnaNamburu marked this conversation as resolved.
Show resolved Hide resolved
"File to provide entry for building project",
{},
];
const buildConfigOpt: opt = [
"--build-config <file>",
JayaKrishnaNamburu marked this conversation as resolved.
Show resolved Hide resolved
"Path to a rollup config file",
{},
];

cli
.option(...silentOpt)
Expand Down Expand Up @@ -278,6 +291,12 @@ Clears the global module fetch cache, for situations where the contents of a dep
.alias("cc")
.action(wrapCommand(clearCache));

cli
.command("build", "Build the module using importmap")
.option(...buildOpt)
.option(...buildConfigOpt)
.action(wrapCommand(build));

// Taken from 'cac', as they don't export it:
interface HelpSection {
title?: string;
Expand Down
8 changes: 7 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Generator } from "@jspm/generator";

export interface Flags {
export interface Flags extends BuildFlags {
resolution?: string | string[];
env?: string | string[];
map?: string;
Expand All @@ -16,6 +16,12 @@ export interface Flags {
cache?: string;
}

export interface BuildFlags {
entry?: string;
outdir?: string;
buildConfig?: string;
}

export type IImportMap = ReturnType<Generator["getMap"]>;

// JSPM adds a non-standard "env" field to import maps, which is used to
Expand Down
42 changes: 24 additions & 18 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import fs from "fs/promises";
import path from "path";
import { pathToFileURL } from "url";
import fs from "node:fs/promises";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { Generator, analyzeHtml } from "@jspm/generator";
import ora from "ora";
import c from "picocolors";
Expand Down Expand Up @@ -205,7 +205,7 @@ export async function getGenerator(
defaultProvider: getProvider(flags),
resolutions: getResolutions(flags),
cache: getCacheMode(flags),
freeze: flags.freeze,
freeze: flags?.freeze || false,
commonJS: true, // TODO: only for --local flag
});
}
Expand Down Expand Up @@ -247,13 +247,13 @@ async function getInputMap(flags: Flags): Promise<IImportMapJspm> {
}

export function getInputPath(flags: Flags): string {
return path.resolve(process.cwd(), flags.map || defaultInputPath);
return path.resolve(process.cwd(), flags?.map || defaultInputPath);
}

export function getOutputPath(flags: Flags): string | undefined {
return path.resolve(
process.cwd(),
flags.output || flags.map || defaultInputPath
flags?.output || flags?.map || defaultInputPath
);
}

Expand All @@ -262,7 +262,7 @@ function getOutputMapUrl(flags: Flags): URL {
}

function getRootUrl(flags: Flags): URL {
if (!flags.root) return undefined;
if (!flags?.root) return undefined;
return pathToFileURL(path.resolve(process.cwd(), flags.root));
}

Expand Down Expand Up @@ -295,9 +295,9 @@ function addEnvs(env: string[], newEnvs: string[]) {

export async function getEnv(flags: Flags) {
const inputMap = await getInputMap(flags);
const envFlags = Array.isArray(flags.env)
? flags.env
: (flags.env || "")
const envFlags = Array.isArray(flags?.env)
? flags?.env
: (flags?.env || "")
.split(",")
.map((e) => e.trim())
.filter(Boolean);
Expand All @@ -314,14 +314,18 @@ export async function getEnv(flags: Flags) {
return removeNonStaticEnvKeys(env);
}

function getProvider(flags: Flags) {
if (flags.provider && !availableProviders.includes(flags.provider))
function getProvider(flags: Flags): (typeof availableProviders)[number] {
if (!flags?.provider) {
return "jspm.io";
}

if (flags?.provider && !availableProviders.includes(flags.provider))
throw new JspmError(
`Invalid provider "${
flags.provider
}". Available providers are: "${availableProviders.join('", "')}".`
);
return flags.provider;
return flags?.provider;
}

function removeNonStaticEnvKeys(env: string[]) {
Expand All @@ -331,7 +335,7 @@ function removeNonStaticEnvKeys(env: string[]) {
}

function getResolutions(flags: Flags): Record<string, string> {
if (!flags.resolution) return;
if (!flags?.resolution) return;
const resolutions = Array.isArray(flags.resolution)
? flags.resolution
: flags.resolution.split(",").map((r) => r.trim());
Expand All @@ -352,7 +356,7 @@ function getResolutions(flags: Flags): Record<string, string> {

const validCacheModes = ["online", "offline", "no-cache"];
function getCacheMode(flags: Flags): "offline" | boolean {
if (!flags.cache) return true;
if (!flags?.cache) return true;
if (!validCacheModes.includes(flags.cache))
throw new JspmError(
`Invalid cache mode "${
Expand All @@ -372,7 +376,7 @@ function getCacheMode(flags: Flags): "offline" | boolean {
}

const validPreloadModes = ["static", "dynamic"];
function getPreloadMode(flags: Flags): boolean | 'static' | 'all' {
function getPreloadMode(flags: Flags): boolean | "static" | "all" {
if (flags.preload === null || flags.preload === undefined) return false;
if (typeof flags.preload === "boolean") {
return flags.preload;
Expand All @@ -382,7 +386,9 @@ function getPreloadMode(flags: Flags): boolean | 'static' | 'all' {
throw new JspmError(
`Invalid preload mode "${
flags.preload
}". Available modes are: "${validPreloadModes.join('", "')}" (default).\n\t${c.bold(
}". Available modes are: "${validPreloadModes.join(
'", "'
)}" (default).\n\t${c.bold(
"static"
)} Inject preload tags for static dependencies.\n\t${c.bold(
"dynamic"
Expand All @@ -403,7 +409,7 @@ export function stopSpinner() {
spinner.stop();
}

async function exists(file: string) {
export async function exists(file: string) {
try {
await fs.access(file);
return true;
Expand Down
Loading