Skip to content

Commit

Permalink
Revert file interface (#541)
Browse files Browse the repository at this point in the history
* Add standard file interface

* Update transformFileImports error message

* PR feedback

* Fix extra dot

* Add CDN test
  • Loading branch information
drwpow committed Jun 24, 2020
1 parent 5653cdf commit b538d68
Show file tree
Hide file tree
Showing 19 changed files with 273 additions and 116 deletions.
83 changes: 46 additions & 37 deletions src/commands/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import mkdirp from 'mkdirp';
import npmRunPath from 'npm-run-path';
import path from 'path';
import rimraf from 'rimraf';
import {BuildScript} from '../config';
import {BuildScript, SnowpackBuildMap, SnowpackSourceFile} from '../config';
import {transformFileImports} from '../rewrite-imports';
import {printStats} from '../stats-formatter';
import {CommandOptions} from '../util';
import {CommandOptions, getExt} from '../util';
import {
generateEnvModule,
getFileBuilderForWorker,
Expand All @@ -26,7 +26,7 @@ import {paint} from './paint';
import srcFileExtensionMapping from './src-file-extension-mapping';

async function installOptimizedDependencies(
allFilesToResolveImports: {outLoc: string; code: string}[],
allFilesToResolveImports: SnowpackBuildMap,
installDest: string,
commandOptions: CommandOptions,
) {
Expand All @@ -41,7 +41,7 @@ async function installOptimizedDependencies(
// 1. Scan imports from your final built JS files.
const installTargets = await getInstallTargets(
installConfig,
allFilesToResolveImports.map(({outLoc, code}) => [outLoc, code]),
Object.values(allFilesToResolveImports),
);
// 2. Install dependencies, based on the scan of your final build.
const installResult = await installRunner({
Expand Down Expand Up @@ -213,12 +213,13 @@ export async function command(commandOptions: CommandOptions) {
await Promise.all(
allFiles.map(async (f) => {
f = path.resolve(f); // this is necessary since glob.sync() returns paths with / on windows. path.resolve() will switch them to the native path separator.
const {baseExt} = getExt(f);
if (
allBuildExtensions.includes(path.extname(f).substr(1)) ||
path.extname(f) === '.jsx' ||
path.extname(f) === '.tsx' ||
path.extname(f) === '.ts' ||
path.extname(f) === '.js'
allBuildExtensions.includes(baseExt.substr(1)) ||
baseExt === '.jsx' ||
baseExt === '.tsx' ||
baseExt === '.ts' ||
baseExt === '.js'
) {
allBuildNeededFiles.push(f);
return;
Expand All @@ -227,7 +228,7 @@ export async function command(commandOptions: CommandOptions) {
mkdirp.sync(path.dirname(outPath));

// replace %PUBLIC_URL% in HTML files
if (path.extname(f) === '.html') {
if (baseExt === '.html') {
let code = await fs.readFile(f, 'utf8');
code = code.replace(/%PUBLIC_URL%\/?/g, config.buildOptions.baseUrl);
return fs.writeFile(outPath, code, 'utf8');
Expand All @@ -244,7 +245,7 @@ export async function command(commandOptions: CommandOptions) {
}

const allBuiltFromFiles = new Set<string>();
const allFilesToResolveImports: {outLoc: string; code: string; fileLoc: string}[] = [];
const allFilesToResolveImports: SnowpackBuildMap = {};
for (const workerConfig of relevantWorkers) {
const {id, match, type} = workerConfig;
if (type !== 'build' || match.length === 0) {
Expand All @@ -253,26 +254,25 @@ export async function command(commandOptions: CommandOptions) {

messageBus.emit('WORKER_UPDATE', {id, state: ['RUNNING', 'yellow']});
for (const [dirDisk, dirDest, allFiles] of includeFileSets) {
for (const fileLoc of allFiles) {
const fileExtension = path.extname(fileLoc).substr(1);
if (!match.includes(fileExtension)) {
for (const locOnDisk of allFiles) {
const inputExt = getExt(locOnDisk);
if (!match.includes(inputExt.baseExt) && !match.includes(inputExt.expandedExt)) {
continue;
}
const fileContents = await fs.readFile(fileLoc, {encoding: 'utf8'});
const fileContents = await fs.readFile(locOnDisk, {encoding: 'utf8'});
let fileBuilder = getFileBuilderForWorker(cwd, workerConfig, messageBus);
if (!fileBuilder) {
continue;
}
let outLoc = fileLoc.replace(dirDisk, dirDest);
const extToFind = path.extname(fileLoc).substr(1);
const extToReplace = srcFileExtensionMapping[extToFind];
let outLoc = locOnDisk.replace(dirDisk, dirDest);
const extToReplace = srcFileExtensionMapping[inputExt.baseExt];
if (extToReplace) {
outLoc = outLoc.replace(new RegExp(`${extToFind}$`), extToReplace!);
outLoc = outLoc.replace(new RegExp(`\\${inputExt.baseExt}$`), extToReplace!);
}

const builtFile = await fileBuilder({
contents: fileContents,
filePath: fileLoc,
filePath: locOnDisk,
isDev: false,
});
if (!builtFile) {
Expand All @@ -291,21 +291,30 @@ export async function command(commandOptions: CommandOptions) {
continue;
}

allBuiltFromFiles.add(fileLoc);
if (path.extname(outLoc) === '.js') {
if (resources?.css) {
const cssOutPath = outLoc.replace(/.js$/, '.css');
await fs.mkdir(path.dirname(cssOutPath), {recursive: true});
await fs.writeFile(cssOutPath, resources.css);
code = `import './${path.basename(cssOutPath)}';\n` + code;
allBuiltFromFiles.add(locOnDisk);

const {baseExt, expandedExt} = getExt(outLoc);
switch (baseExt) {
case '.js': {
if (resources?.css) {
const cssOutPath = outLoc.replace(/.js$/, '.css');
await fs.mkdir(path.dirname(cssOutPath), {recursive: true});
await fs.writeFile(cssOutPath, resources.css);
code = `import './${path.basename(cssOutPath)}';\n` + code;
}
code = wrapImportMeta({code, env: true, hmr: false, config});
allFilesToResolveImports[outLoc] = {baseExt, expandedExt, code, locOnDisk};
break;
}
case '.html': {
allFilesToResolveImports[outLoc] = {baseExt, expandedExt, code, locOnDisk};
break;
}
default: {
await fs.mkdir(path.dirname(outLoc), {recursive: true});
await fs.writeFile(outLoc, code);
break;
}
code = wrapImportMeta({code, env: true, hmr: false, config});
allFilesToResolveImports.push({outLoc, code, fileLoc});
} else if (path.extname(outLoc) === '.html') {
allFilesToResolveImports.push({outLoc, code, fileLoc});
} else {
await fs.mkdir(path.dirname(outLoc), {recursive: true});
await fs.writeFile(outLoc, code);
}
}
}
Expand All @@ -326,16 +335,16 @@ export async function command(commandOptions: CommandOptions) {
}

const allProxiedFiles = new Set<string>();
for (const {outLoc, code, fileLoc} of allFilesToResolveImports) {
for (const [outLoc, file] of Object.entries(allFilesToResolveImports)) {
const resolveImportSpecifier = createImportResolver({
fileLoc,
fileLoc: file.locOnDisk!, // we’re confident these are reading from disk because we just read them
webModulesPath,
dependencyImportMap: installResult.importMap,
isDev: false,
isBundled,
config,
});
const resolvedCode = await transformFileImports(code, path.extname(outLoc), (spec) => {
const resolvedCode = await transformFileImports(file, (spec) => {
// Try to resolve the specifier to a known URL in the project
const resolvedImportUrl = resolveImportSpecifier(spec);
if (resolvedImportUrl) {
Expand Down
31 changes: 17 additions & 14 deletions src/commands/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import {
openInBrowser,
resolveDependencyManifest,
updateLockfileHash,
getExt,
} from '../util';
import {
FileBuilder,
Expand Down Expand Up @@ -171,12 +172,12 @@ const sendError = (res, status) => {
function getUrlFromFile(mountedDirectories: [string, string][], fileLoc: string): string | null {
for (const [dirDisk, dirUrl] of mountedDirectories) {
if (fileLoc.startsWith(dirDisk + path.sep)) {
const fileExt = path.extname(fileLoc).substr(1);
const {baseExt} = getExt(fileLoc);
const resolvedDirUrl = dirUrl === '/' ? '' : dirUrl;
return fileLoc
.replace(dirDisk, resolvedDirUrl)
.replace(/[/\\]+/g, '/')
.replace(new RegExp(`${fileExt}$`), srcFileExtensionMapping[fileExt] || fileExt);
.replace(new RegExp(`\\${baseExt}$`), srcFileExtensionMapping[baseExt] || baseExt);
}
}
return null;
Expand Down Expand Up @@ -345,12 +346,12 @@ export async function command(commandOptions: CommandOptions) {
filesBeingBuilt.delete(fileLoc);
}
}
const ext = path.extname(fileLoc).substr(1);
const {baseExt, expandedExt} = getExt(fileLoc);
if (
ext === 'js' ||
srcFileExtensionMapping[ext] === 'js' ||
ext === 'html' ||
srcFileExtensionMapping[ext] === 'html'
baseExt === '.js' ||
srcFileExtensionMapping[baseExt] === '.js' ||
baseExt === '.html' ||
srcFileExtensionMapping[baseExt] === '.html'
) {
let missingWebModule: {spec: string; pkgName: string} | null = null;
const webModulesScript = config.scripts.find((script) => script.id === 'mount:web_modules');
Expand All @@ -364,10 +365,12 @@ export async function command(commandOptions: CommandOptions) {
config,
});
builtFileResult.result = await transformFileImports(
builtFileResult.result,
// This is lame: because routes don't have a file extension, we create
// a fake file name with the correct extension, which is the important bit.
'file.' + (srcFileExtensionMapping[ext] || ext),
{
locOnDisk: fileLoc,
code: builtFileResult.result,
baseExt: srcFileExtensionMapping[baseExt] || baseExt,
expandedExt,
},
(spec) => {
// Try to resolve the specifier to a known URL in the project
const resolvedImportUrl = resolveImportSpecifier(spec);
Expand Down Expand Up @@ -611,10 +614,10 @@ export async function command(commandOptions: CommandOptions) {
}
for (const extMatcher of match) {
if (
extMatcher === requestedFileExt.substr(1) ||
srcFileExtensionMapping[extMatcher] === requestedFileExt.substr(1)
extMatcher === requestedFileExt ||
srcFileExtensionMapping[extMatcher] === requestedFileExt
) {
const srcFile = requestedFile.replace(requestedFileExt, `.${extMatcher}`);
const srcFile = requestedFile.replace(requestedFileExt, extMatcher);
const fileLoc = await attemptLoadFile(srcFile);
if (fileLoc) {
return [fileLoc, workerConfig];
Expand Down
10 changes: 5 additions & 5 deletions src/commands/import-resolver.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {Stats, statSync} from 'fs';
import path from 'path';
import {SnowpackConfig} from '../config';
import {findMatchingMountScript, ImportMap} from '../util';
import {findMatchingMountScript, getExt, ImportMap} from '../util';
import srcFileExtensionMapping from './src-file-extension-mapping';
const cwd = process.cwd();
const URL_HAS_PROTOCOL_REGEX = /^(\w+:)?\/\//;
Expand Down Expand Up @@ -34,12 +34,12 @@ function resolveSourceSpecifier(spec: string, stats: Stats | false, isBundled: b
} else if (!stats && !spec.endsWith('.js')) {
spec = spec + '.js';
}
const ext = path.extname(spec).substr(1);
const extToReplace = srcFileExtensionMapping[ext];
const {baseExt} = getExt(spec);
const extToReplace = srcFileExtensionMapping[baseExt];
if (extToReplace) {
spec = spec.replace(new RegExp(`${ext}$`), extToReplace);
spec = spec.replace(new RegExp(`\\${baseExt}$`), extToReplace);
}
if (!isBundled && (extToReplace || ext) !== 'js') {
if (!isBundled && (extToReplace || baseExt) !== '.js') {
spec = spec + '.proxy.js';
}

Expand Down
7 changes: 5 additions & 2 deletions src/commands/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import path from 'path';
import rimraf from 'rimraf';
import {InputOptions, OutputOptions, rollup, RollupError} from 'rollup';
import validatePackageName from 'validate-npm-package-name';
import {EnvVarReplacements, SnowpackConfig} from '../config.js';
import {EnvVarReplacements, SnowpackConfig, SnowpackSourceFile} from '../config.js';
import {resolveTargetsFromRemoteCDN} from '../resolve-remote.js';
import {rollupPluginCatchUnresolved} from '../rollup-plugin-catch-unresolved.js';
import {rollupPluginCss} from '../rollup-plugin-css';
Expand Down Expand Up @@ -466,7 +466,10 @@ export async function install(
return {success: true, importMap};
}

export async function getInstallTargets(config: SnowpackConfig, scannedFiles?: [string, string][]) {
export async function getInstallTargets(
config: SnowpackConfig,
scannedFiles?: SnowpackSourceFile[],
) {
const {knownEntrypoints, webDependencies} = config;
const installTargets: InstallTarget[] = [];
if (knownEntrypoints) {
Expand Down
36 changes: 18 additions & 18 deletions src/commands/src-file-extension-mapping.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
export default {
mjs: 'js',
jsx: 'js',
ts: 'js',
tsx: 'js',
vue: 'js',
svelte: 'js',
mdx: 'js',
svx: 'js',
elm: 'js',
yaml: 'json',
toml: 'json',
php: 'html',
md: 'html',
ejs: 'html',
njk: 'html',
scss: 'css',
sass: 'css',
less: 'css',
'.mjs': '.js',
'.jsx': '.js',
'.ts': '.js',
'.tsx': '.js',
'.vue': '.js',
'.svelte': '.js',
'.mdx': '.js',
'.svx': '.js',
'.elm': '.js',
'.yaml': '.json',
'.toml': '.json',
'.php': '.html',
'.md': '.html',
'.ejs': '.html',
'.njk': '.html',
'.scss': '.css',
'.sass': '.css',
'.less': '.css',
};
24 changes: 21 additions & 3 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,20 @@ type DeepPartial<T> = {

export type EnvVarReplacements = Record<string, string | number | true>;

/** Snowpack export map */
export type SnowpackBuildMap = {[outputLoc: string]: SnowpackSourceFile};

/** Standard file interface */
export interface SnowpackSourceFile {
/** base extension (e.g. `.js`) */
baseExt: string;
/** file contents */
code: string;
/** expanded extension (e.g. `.proxy.js` or `.module.css`) */
expandedExt: string;
/** if no location on disk, assume this exists in memory */
locOnDisk?: string;
}
export type SnowpackPluginBuildArgs = {
contents: string;
filePath: string;
Expand Down Expand Up @@ -316,6 +330,10 @@ function handleLegacyProxyScripts(config: any) {

type RawScripts = Record<string, string>;
function normalizeScripts(cwd: string, scripts: RawScripts): BuildScript[] {
function prefixDot(file: string): string {
return `.${file}`.replace(/^\.+/, '.'); // prefix with dot, and make sure only one sticks
}

const processedScripts: BuildScript[] = [];
if (Object.keys(scripts).filter((k) => k.startsWith('bundle:')).length > 1) {
handleConfigError(`scripts can only contain 1 script of type "bundle:".`);
Expand All @@ -332,7 +350,7 @@ function normalizeScripts(cwd: string, scripts: RawScripts): BuildScript[] {
const newScriptConfig: BuildScript = {
id: scriptId,
type: scriptType,
match: scriptMatch.split(','),
match: scriptMatch.split(',').map(prefixDot),
cmd,
watch: (scripts[`${scriptId}::watch`] as any) as string | undefined,
};
Expand Down Expand Up @@ -381,7 +399,7 @@ function normalizeScripts(cwd: string, scripts: RawScripts): BuildScript[] {
`Multiple "scripts" match the "${ext}" file extension.\nCurrently, only one script per file type is supported.`,
);
}
allBuildMatch.add(ext);
allBuildMatch.add(prefixDot(ext));
}
}

Expand All @@ -398,7 +416,7 @@ function normalizeScripts(cwd: string, scripts: RawScripts): BuildScript[] {
});
}

const defaultBuildMatch = ['js', 'jsx', 'ts', 'tsx'].filter((ext) => !allBuildMatch.has(ext));
const defaultBuildMatch = ['.js', '.jsx', '.ts', '.tsx'].filter((ext) => !allBuildMatch.has(ext));
if (defaultBuildMatch.length > 0) {
const defaultBuildWorkerConfig = {
id: `build:${defaultBuildMatch.join(',')}`,
Expand Down

1 comment on commit b538d68

@vercel
Copy link

@vercel vercel bot commented on b538d68 Jun 24, 2020

Choose a reason for hiding this comment

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

Please sign in to comment.