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

Support secondary file extensions in resolve.input & resolve.output #… #1909

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 43 additions & 37 deletions snowpack/src/build/build-pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,19 @@ export interface BuildFileOptions {

export function getInputsFromOutput(fileLoc: string, plugins: SnowpackPlugin[]) {
const srcFile = replaceExt(fileLoc, '.map', ''); // if this is a .map file, try loading source
const {baseExt} = getExt(srcFile);
const {expandedExt} = getExt(srcFile);

const potentialInputs = new Set([srcFile]);
for (const plugin of plugins) {
if (plugin.resolve && plugin.resolve.output.includes(baseExt)) {
plugin.resolve.input.forEach((input) =>
potentialInputs.add(replaceExt(srcFile, baseExt, input)),
);
if (expandedExt) for (const plugin of plugins) {
if (plugin.resolve) {
for (let outExt of plugin.resolve.output) {
if (expandedExt.endsWith(outExt)) {
plugin.resolve.input.forEach((input) =>
potentialInputs.add(replaceExt(srcFile, outExt, input))
);
break;
}
}
}
}
return Array.from(potentialInputs);
Expand All @@ -47,15 +52,19 @@ async function runPipelineLoadStep(
srcPath: string,
{isDev, isSSR, isHmrEnabled, plugins, sourceMaps}: BuildFileOptions,
): Promise<SnowpackBuildMap> {
const srcExt = getExt(srcPath).baseExt;
const {baseExt, expandedExt} = getExt(srcPath);

for (const step of plugins) {
if (!step.resolve || !step.resolve.input.includes(srcExt)) {
if (!step.load) {
continue;
}
if (!step.load) {
if (!step.resolve) {
continue;
}
let srcExt = step.resolve.input.find(input => expandedExt.endsWith(input));
if (!srcExt) {
continue;
}

try {
const debugPath = path.relative(process.cwd(), srcPath);
logger.debug(`load() starting… [${debugPath}]`, {name: step.name});
Expand All @@ -75,28 +84,32 @@ async function runPipelineLoadStep(
return {
[mainOutputExt]: {
code: result,
outPath: replaceExt(srcPath, srcExt, mainOutputExt)
},
};
} else if (result && typeof result === 'object') {
Object.keys(result).forEach((ext) => {
const output = result[ext];
let out = {};
for (const [ext, output] of Object.entries(result)) {
const outPath = replaceExt(srcPath, srcExt, ext);

// normalize to {code, map} format
if (typeof output === 'string' || Buffer.isBuffer(output)) {
result[ext] = {code: output};
out[ext] = {code: output, outPath: outPath};
} else {
out[ext] = {...output, outPath: outPath};
}

// ensure source maps are strings (it’s easy for plugins to pass back a JSON object)
if (result[ext].map && typeof result[ext].map === 'object')
result[ext].map = JSON.stringify(result[ext].map);
if (out[ext].map && typeof out[ext].map === 'object')
out[ext].map = JSON.stringify(out[ext].map);

// if source maps disabled, don’t return any
if (!sourceMaps) result[ext].map = undefined;
if (!sourceMaps) out[ext].map = undefined;

// clean up empty files
if (!result[ext].code) delete result[ext];
});
return result;
if (!out[ext].code) delete out[ext];
}
return out;
}
} catch (err) {
// Attach metadata detailing where the error occurred.
Expand All @@ -106,8 +119,9 @@ async function runPipelineLoadStep(
}

return {
[srcExt]: {
[baseExt]: {
code: await readFile(url.pathToFileURL(srcPath)),
outPath: srcPath
},
};
}
Expand Down Expand Up @@ -140,36 +154,28 @@ async function composeSourceMaps(
*/
async function runPipelineTransformStep(
output: SnowpackBuildMap,
srcPath: string,
{isDev, plugins, sourceMaps}: BuildFileOptions,
): Promise<SnowpackBuildMap> {
const srcExt = getExt(srcPath).baseExt;
const rootFilePath = srcPath.replace(srcExt, '');
const rootFileName = path.basename(rootFilePath);
for (const step of plugins) {
if (!step.transform) {
continue;
}

try {
for (const destExt of Object.keys(output)) {
const destBuildFile = output[destExt];
const {code} = destBuildFile;
const fileName = rootFileName + destExt;
const filePath = rootFilePath + destExt;
const debugPath = path.relative(process.cwd(), filePath);
logger.debug(`transform() starting… [${debugPath}]`, {name: step.name});
const {code, outPath} = output[destExt];
const filePath = path.basename(outPath);
logger.debug(`transform() starting… [${outPath}]`, {name: step.name});
const result = await step.transform({
contents: code,
isDev,
fileExt: destExt,
id: filePath,
id: outPath,
// @ts-ignore: Deprecated
filePath: fileName,
filePath: filePath,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems to be changing a lot of existing behavior to use new values. our entire plugin ecosystem relies on these values not changing

// @ts-ignore: Deprecated
urlPath: `./${path.basename(rootFileName + destExt)}`,
urlPath: `./${filePath}`,
});
logger.debug(`✔ transform() success [${debugPath}]`, {name: step.name});
logger.debug(`✔ transform() success [${outPath}]`, {name: step.name});
if (typeof result === 'string' || Buffer.isBuffer(result)) {
// V2 API, simple string variant
output[destExt].code = result;
Expand All @@ -186,7 +192,7 @@ async function runPipelineTransformStep(
if (map && sourceMaps) {
// if source maps disabled, don’t return any
if (output[destExt].map) {
outputMap = await composeSourceMaps(filePath, output[destExt].map!, map);
outputMap = await composeSourceMaps(outPath, output[destExt].map!, map);
} else {
outputMap = typeof map === 'object' ? JSON.stringify(map) : map;
}
Expand Down Expand Up @@ -253,7 +259,7 @@ export async function buildFile(
// Pass 1: Find the first plugin to load this file, and return the result
const loadResult = await runPipelineLoadStep(url.fileURLToPath(srcURL), buildFileOptions);
// Pass 2: Pass that result through every plugin transform() method.
const transformResult = await runPipelineTransformStep(loadResult, url.fileURLToPath(srcURL), buildFileOptions);
const transformResult = await runPipelineTransformStep(loadResult, buildFileOptions);
// Return the final build result.
return transformResult;
}
19 changes: 11 additions & 8 deletions snowpack/src/build/file-urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,18 @@ export function getUrlForFileMount({
mountEntry: MountEntry;
config: SnowpackConfig;
}): string {
const {baseExt} = getExt(fileLoc);
const {expandedExt} = getExt(fileLoc);
const resolvedDirUrl = mountEntry.url === '/' ? '' : mountEntry.url;
return replaceExt(
fileLoc.replace(mountKey, resolvedDirUrl).replace(/[/\\]+/g, '/'),
baseExt,
mountEntry.static
? baseExt
: config._extensionMap[baseExt] || baseExt,
);
let renamedFileLoc = fileLoc.replace(mountKey, resolvedDirUrl).replace(/[/\\]+/g, "/");
if (!mountEntry.static) {
for (let [fromExt, toExt] of Object.entries(config._extensionMap)) {
if (expandedExt.endsWith(fromExt)) {
renamedFileLoc = replaceExt(renamedFileLoc, fromExt, toExt);
break;
}
}
}
return renamedFileLoc;
}

/**
Expand Down
10 changes: 6 additions & 4 deletions snowpack/src/build/import-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ function resolveSourceSpecifier(spec: string, stats: fs.Stats | false, config: S
spec = spec + trailingSlash + 'index.js';
}
// Transform the file extension (from input to output)
const {baseExt} = getExt(spec);
const extToReplace = config._extensionMap[baseExt];
if (extToReplace) {
spec = replaceExt(spec, baseExt, extToReplace);
const {expandedExt} = getExt(spec);
for (let [fromExt, toExt] of Object.entries(config._extensionMap)) {
if (expandedExt.endsWith(fromExt)) {
spec = replaceExt(spec, fromExt, toExt);
break;
}
}
// Lazy check to handle imports that are missing file extensions
if (!stats && !spec.endsWith('.js') && !spec.endsWith('.css')) {
Expand Down
13 changes: 7 additions & 6 deletions snowpack/src/commands/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,10 @@ class FileBuilder {
async buildFile() {
this.filesToResolve = {};
const isSSR = this.config.experiments.ssr;
const srcExt = path.extname(url.fileURLToPath(this.fileURL));
const filePath = url.fileURLToPath(this.fileURL);
const srcExt = path.extname(filePath);
const fileOutput = this.mountEntry.static
? {[srcExt]: {code: await readFile(this.fileURL)}}
? {[srcExt]: {code: await readFile(this.fileURL), outPath:filePath}}
: await buildFile(this.fileURL, {
plugins: this.config.plugins,
isDev: false,
Expand All @@ -161,7 +162,7 @@ class FileBuilder {
continue;
}
const outFilename = replaceExt(
path.basename(url.fileURLToPath(this.fileURL)),
path.basename(filePath),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we're trying to use more file URLs instead of less, so would like to keep these as-is

srcExt,
fileExt,
);
Expand All @@ -175,7 +176,7 @@ class FileBuilder {
baseExt: fileExt,
expandedExt: fileExt,
contents: code,
locOnDisk: url.fileURLToPath(this.fileURL),
locOnDisk: filePath,
};
break;
}
Expand All @@ -192,7 +193,7 @@ class FileBuilder {
baseExt: fileExt,
expandedExt: fileExt,
contents: code,
locOnDisk: url.fileURLToPath(this.fileURL),
locOnDisk: filePath,
};
break;
}
Expand All @@ -210,7 +211,7 @@ class FileBuilder {
baseExt: fileExt,
expandedExt: fileExt,
contents: code,
locOnDisk: url.fileURLToPath(this.fileURL),
locOnDisk: filePath,
};
break;
}
Expand Down
9 changes: 5 additions & 4 deletions snowpack/src/commands/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -844,11 +844,11 @@ export async function startDevServer(commandOptions: CommandOptions): Promise<Sn
requestedFileExt: string,
output: SnowpackBuildMap,
): Promise<string | Buffer | null> {
// Verify that the requested file exists in the build output map.
if (!output[requestedFileExt] || !Object.keys(output)) {
let matchingExtendedExt = Object.keys(output).find(key => key.endsWith(requestedFileExt));
if (!matchingExtendedExt) {
return null;
}
const {code, map} = output[requestedFileExt];
const {code, map} = output[matchingExtendedExt];
let finalResponse = code;
// Handle attached CSS.
if (requestedFileExt === '.js' && output['.css']) {
Expand Down Expand Up @@ -925,7 +925,7 @@ export async function startDevServer(commandOptions: CommandOptions): Promise<Sn
let responseContent: string | Buffer | null;
try {
responseContent = await finalizeResponse(fileLoc, requestedFileExt, {
[requestedFileExt]: {code: fileContents},
[requestedFileExt]: {code: fileContents, outPath: fileLoc},
});
} catch (err) {
logger.error(FILE_BUILD_RESULT_ERROR);
Expand Down Expand Up @@ -974,6 +974,7 @@ export async function startDevServer(commandOptions: CommandOptions): Promise<Sn
{
code: string;
map?: string;
outPath: string;
}
>;
inMemoryBuildCache.set(
Expand Down
6 changes: 5 additions & 1 deletion snowpack/src/types/snowpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export interface SnowpackBuildResult {
export type SnowpackBuiltFile = {
code: string | Buffer;
map?: string;
outPath: string
};

export type SnowpackBuildMap = Record<string, SnowpackBuiltFile>;
Expand Down Expand Up @@ -117,7 +118,10 @@ export interface PluginRunOptions {
}

/** map of extensions -> code (e.g. { ".js": "[code]", ".css": "[code]" }) */
export type PluginLoadResult = SnowpackBuildMap;
export type PluginLoadResult = Record<string, {
code: string | Buffer;
map?: string;
}>;

export type PluginTransformResult = {contents: string; map: string | RawSourceMap};

Expand Down