From 6361c2f2646163dca272611e60dd379a9312841d Mon Sep 17 00:00:00 2001 From: Rene Haas Date: Sat, 3 Sep 2022 19:53:58 +0200 Subject: [PATCH 01/17] create and add css file plugin --- packages/remix-dev/compiler.ts | 3 + .../compiler/plugins/cssFilePlugin.ts | 62 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 packages/remix-dev/compiler/plugins/cssFilePlugin.ts diff --git a/packages/remix-dev/compiler.ts b/packages/remix-dev/compiler.ts index b677af3ca97..d8697957c9a 100644 --- a/packages/remix-dev/compiler.ts +++ b/packages/remix-dev/compiler.ts @@ -23,6 +23,7 @@ import { serverAssetsManifestPlugin } from "./compiler/plugins/serverAssetsManif import { serverBareModulesPlugin } from "./compiler/plugins/serverBareModulesPlugin"; import { serverEntryModulePlugin } from "./compiler/plugins/serverEntryModulePlugin"; import { serverRouteModulesPlugin } from "./compiler/plugins/serverRouteModulesPlugin"; +import { cssFilePlugin } from "./compiler/plugins/cssFilePlugin"; import { writeFileSafe } from "./compiler/utils/fs"; import { urlImportsPlugin } from "./compiler/plugins/urlImportsPlugin"; @@ -347,6 +348,7 @@ async function createBrowserBuild( } let plugins = [ + cssFilePlugin(config), urlImportsPlugin(), mdxPlugin(config), browserRouteModulesPlugin(config, /\?browser$/), @@ -415,6 +417,7 @@ function createServerBuild( let isDenoRuntime = config.serverBuildTarget === "deno"; let plugins: esbuild.Plugin[] = [ + cssFilePlugin(config), urlImportsPlugin(), mdxPlugin(config), emptyModulesPlugin(config, /\.client(\.[jt]sx?)?$/), diff --git a/packages/remix-dev/compiler/plugins/cssFilePlugin.ts b/packages/remix-dev/compiler/plugins/cssFilePlugin.ts new file mode 100644 index 00000000000..9bf9abd7f8c --- /dev/null +++ b/packages/remix-dev/compiler/plugins/cssFilePlugin.ts @@ -0,0 +1,62 @@ +import * as path from "path"; +import esbuild from "esbuild"; + +import type { RemixConfig } from "../../config"; + +/** + * This plugin loads css files with the "css" loader (bundles and moves assets to assets directory) + * and exports the url of the css file as its default export. + */ +export function cssFilePlugin(config: RemixConfig): esbuild.Plugin { + return { + name: "css-file", + + async setup(build) { + let buildOptions = build.initialOptions; + + build.onLoad({ filter: /\.css$/ }, async (args) => { + let { outfile, outdir, assetNames } = buildOptions; + let assetDirname = path.dirname(assetNames!); + let { metafile } = await esbuild.build({ + ...buildOptions, + minifySyntax: false, + incremental: false, + splitting: false, + sourcemap: false, + write: true, + stdin: undefined, + outfile: undefined, + outdir: path.join( + outfile ? path.dirname(outfile)! : outdir!, + assetDirname + ), + entryPoints: [args.path], + assetNames: "[name]-[hash]", + entryNames: "[dir]/[name]-[hash]", + publicPath: ".", + loader: { + ...buildOptions.loader, + ".css": "css", + }, + metafile: true, + plugins: [], + }); + + let keys = Object.keys(metafile.outputs); + let entry = keys.find((key) => { + let { entryPoint } = metafile.outputs[key]; + return !!entryPoint; + })!; + + return { + contents: `export default "${path.join( + config.publicPath, + assetDirname, + path.basename(entry) + )}";`, + loader: "js", + }; + }); + }, + }; +} From 6f3332e666c949f3dcf5c282dbdc93ba7ef31b24 Mon Sep 17 00:00:00 2001 From: Rene Haas Date: Sat, 3 Sep 2022 20:00:21 +0200 Subject: [PATCH 02/17] Update contributors.yml --- contributors.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/contributors.yml b/contributors.yml index 4164fec2abb..89f0d62c2f1 100644 --- a/contributors.yml +++ b/contributors.yml @@ -223,6 +223,7 @@ - kilian - kiliman - kimdontdoit +- KingSora - klauspaiva - knowler - konradkalemba From 4bc56579c1598c3d36920acc083ba175c3742d43 Mon Sep 17 00:00:00 2001 From: Rene Haas Date: Wed, 7 Sep 2022 09:54:45 +0200 Subject: [PATCH 03/17] absolute url paths are considered external --- .../compiler/plugins/cssFilePlugin.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/remix-dev/compiler/plugins/cssFilePlugin.ts b/packages/remix-dev/compiler/plugins/cssFilePlugin.ts index 9bf9abd7f8c..dbe759519ee 100644 --- a/packages/remix-dev/compiler/plugins/cssFilePlugin.ts +++ b/packages/remix-dev/compiler/plugins/cssFilePlugin.ts @@ -39,7 +39,23 @@ export function cssFilePlugin(config: RemixConfig): esbuild.Plugin { ".css": "css", }, metafile: true, - plugins: [], + plugins: [ + { + name: "external-absolute-url", + async setup(cssBuild) { + cssBuild.onResolve({ filter: /.*/ }, async (args) => { + let { kind, path: resolvePath } = args; + if (kind === "url-token" && path.isAbsolute(resolvePath)) { + return { + path: resolvePath, + external: true, + }; + } + return {}; + }); + }, + }, + ], }); let keys = Object.keys(metafile.outputs); From facd26641c248a354ec8149ad39a62792a120770 Mon Sep 17 00:00:00 2001 From: Rene Haas Date: Thu, 8 Sep 2022 11:31:37 +0200 Subject: [PATCH 04/17] add correct sourcemap and minify behavior --- packages/remix-dev/compiler.ts | 6 +++--- .../remix-dev/compiler/plugins/cssFilePlugin.ts | 13 ++++++++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/remix-dev/compiler.ts b/packages/remix-dev/compiler.ts index d8697957c9a..8e30afcc9e5 100644 --- a/packages/remix-dev/compiler.ts +++ b/packages/remix-dev/compiler.ts @@ -27,7 +27,7 @@ import { cssFilePlugin } from "./compiler/plugins/cssFilePlugin"; import { writeFileSafe } from "./compiler/utils/fs"; import { urlImportsPlugin } from "./compiler/plugins/urlImportsPlugin"; -interface BuildConfig { +export interface BuildConfig { mode: BuildMode; target: BuildTarget; sourcemap: boolean; @@ -348,7 +348,7 @@ async function createBrowserBuild( } let plugins = [ - cssFilePlugin(config), + cssFilePlugin(config, options), urlImportsPlugin(), mdxPlugin(config), browserRouteModulesPlugin(config, /\?browser$/), @@ -417,7 +417,7 @@ function createServerBuild( let isDenoRuntime = config.serverBuildTarget === "deno"; let plugins: esbuild.Plugin[] = [ - cssFilePlugin(config), + cssFilePlugin(config, options), urlImportsPlugin(), mdxPlugin(config), emptyModulesPlugin(config, /\.client(\.[jt]sx?)?$/), diff --git a/packages/remix-dev/compiler/plugins/cssFilePlugin.ts b/packages/remix-dev/compiler/plugins/cssFilePlugin.ts index dbe759519ee..17fde849983 100644 --- a/packages/remix-dev/compiler/plugins/cssFilePlugin.ts +++ b/packages/remix-dev/compiler/plugins/cssFilePlugin.ts @@ -1,28 +1,35 @@ import * as path from "path"; import esbuild from "esbuild"; +import { BuildMode } from "../../build"; import type { RemixConfig } from "../../config"; +import type { BuildConfig } from "../../compiler"; /** * This plugin loads css files with the "css" loader (bundles and moves assets to assets directory) * and exports the url of the css file as its default export. */ -export function cssFilePlugin(config: RemixConfig): esbuild.Plugin { +export function cssFilePlugin( + remixConfig: RemixConfig, + buildConfig: Partial +): esbuild.Plugin { return { name: "css-file", async setup(build) { let buildOptions = build.initialOptions; + let { mode, sourcemap } = buildConfig; build.onLoad({ filter: /\.css$/ }, async (args) => { let { outfile, outdir, assetNames } = buildOptions; let assetDirname = path.dirname(assetNames!); let { metafile } = await esbuild.build({ ...buildOptions, + minify: mode === BuildMode.Production, + sourcemap, minifySyntax: false, incremental: false, splitting: false, - sourcemap: false, write: true, stdin: undefined, outfile: undefined, @@ -66,7 +73,7 @@ export function cssFilePlugin(config: RemixConfig): esbuild.Plugin { return { contents: `export default "${path.join( - config.publicPath, + remixConfig.publicPath, assetDirname, path.basename(entry) )}";`, From 797022ac297cf04027446bb1a84942d046f78257 Mon Sep 17 00:00:00 2001 From: Rene Haas Date: Tue, 13 Sep 2022 00:36:50 +0200 Subject: [PATCH 05/17] improve code --- packages/remix-dev/compiler.ts | 4 +- .../compiler/plugins/cssFilePlugin.ts | 49 +++++++------------ 2 files changed, 20 insertions(+), 33 deletions(-) diff --git a/packages/remix-dev/compiler.ts b/packages/remix-dev/compiler.ts index 8e30afcc9e5..2fcfa3770cb 100644 --- a/packages/remix-dev/compiler.ts +++ b/packages/remix-dev/compiler.ts @@ -348,7 +348,7 @@ async function createBrowserBuild( } let plugins = [ - cssFilePlugin(config, options), + cssFilePlugin(options), urlImportsPlugin(), mdxPlugin(config), browserRouteModulesPlugin(config, /\?browser$/), @@ -417,7 +417,7 @@ function createServerBuild( let isDenoRuntime = config.serverBuildTarget === "deno"; let plugins: esbuild.Plugin[] = [ - cssFilePlugin(config, options), + cssFilePlugin(options), urlImportsPlugin(), mdxPlugin(config), emptyModulesPlugin(config, /\.client(\.[jt]sx?)?$/), diff --git a/packages/remix-dev/compiler/plugins/cssFilePlugin.ts b/packages/remix-dev/compiler/plugins/cssFilePlugin.ts index 17fde849983..992eb905c53 100644 --- a/packages/remix-dev/compiler/plugins/cssFilePlugin.ts +++ b/packages/remix-dev/compiler/plugins/cssFilePlugin.ts @@ -1,8 +1,8 @@ import * as path from "path"; +import * as fse from "fs-extra"; import esbuild from "esbuild"; import { BuildMode } from "../../build"; -import type { RemixConfig } from "../../config"; import type { BuildConfig } from "../../compiler"; /** @@ -10,47 +10,39 @@ import type { BuildConfig } from "../../compiler"; * and exports the url of the css file as its default export. */ export function cssFilePlugin( - remixConfig: RemixConfig, buildConfig: Partial ): esbuild.Plugin { return { name: "css-file", async setup(build) { - let buildOptions = build.initialOptions; - let { mode, sourcemap } = buildConfig; + let buildOps = build.initialOptions; build.onLoad({ filter: /\.css$/ }, async (args) => { - let { outfile, outdir, assetNames } = buildOptions; - let assetDirname = path.dirname(assetNames!); + let { outfile, outdir, assetNames } = buildOps; let { metafile } = await esbuild.build({ - ...buildOptions, - minify: mode === BuildMode.Production, - sourcemap, - minifySyntax: false, + ...buildOps, + minify: buildConfig.mode === BuildMode.Production, + minifySyntax: true, + metafile: true, + write: true, + sourcemap: false, incremental: false, splitting: false, - write: true, stdin: undefined, outfile: undefined, - outdir: path.join( - outfile ? path.dirname(outfile)! : outdir!, - assetDirname - ), + outdir: outfile ? path.dirname(outfile) : outdir, + entryNames: assetNames, entryPoints: [args.path], - assetNames: "[name]-[hash]", - entryNames: "[dir]/[name]-[hash]", - publicPath: ".", loader: { - ...buildOptions.loader, + ...buildOps.loader, ".css": "css", }, - metafile: true, plugins: [ { - name: "external-absolute-url", - async setup(cssBuild) { - cssBuild.onResolve({ filter: /.*/ }, async (args) => { + name: "resolve-absolute", + async setup(build) { + build.onResolve({ filter: /.*/ }, async (args) => { let { kind, path: resolvePath } = args; if (kind === "url-token" && path.isAbsolute(resolvePath)) { return { @@ -58,7 +50,6 @@ export function cssFilePlugin( external: true, }; } - return {}; }); }, }, @@ -69,15 +60,11 @@ export function cssFilePlugin( let entry = keys.find((key) => { let { entryPoint } = metafile.outputs[key]; return !!entryPoint; - })!; + }); return { - contents: `export default "${path.join( - remixConfig.publicPath, - assetDirname, - path.basename(entry) - )}";`, - loader: "js", + contents: fse.readFileSync(entry!), + loader: "file", }; }); }, From 54c8b545d79b8b5f76f803394ddec22419a73b05 Mon Sep 17 00:00:00 2001 From: Rene Haas Date: Tue, 13 Sep 2022 00:45:02 +0200 Subject: [PATCH 06/17] add comment why sourcemap false --- packages/remix-dev/compiler/plugins/cssFilePlugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/remix-dev/compiler/plugins/cssFilePlugin.ts b/packages/remix-dev/compiler/plugins/cssFilePlugin.ts index 992eb905c53..00e87ee39af 100644 --- a/packages/remix-dev/compiler/plugins/cssFilePlugin.ts +++ b/packages/remix-dev/compiler/plugins/cssFilePlugin.ts @@ -26,7 +26,7 @@ export function cssFilePlugin( minifySyntax: true, metafile: true, write: true, - sourcemap: false, + sourcemap: false, // hash depends on sourcemap incremental: false, splitting: false, stdin: undefined, From f18bd54c9399b836a11d946680e5957c691dc1ea Mon Sep 17 00:00:00 2001 From: Rene Haas Date: Tue, 13 Sep 2022 00:59:19 +0200 Subject: [PATCH 07/17] improve code and output --- packages/remix-dev/compiler/plugins/cssFilePlugin.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/remix-dev/compiler/plugins/cssFilePlugin.ts b/packages/remix-dev/compiler/plugins/cssFilePlugin.ts index 00e87ee39af..5898d45eeea 100644 --- a/packages/remix-dev/compiler/plugins/cssFilePlugin.ts +++ b/packages/remix-dev/compiler/plugins/cssFilePlugin.ts @@ -63,7 +63,15 @@ export function cssFilePlugin( }); return { - contents: fse.readFileSync(entry!), + /** + * Because the css file was written to the output with the above build + * we would end up with two css files with different hashes in the output if we use the 'file' loader here. + * Thats why we delete the generated css file, so the final css file in the output is the one created by the 'file' loader. + */ + contents: await fse.readFile(entry!).then(async (contents) => { + await fse.remove(entry!); + return contents; + }), loader: "file", }; }); From 000a13a75d0d8167aa0e49a45ca4c11e714eccd9 Mon Sep 17 00:00:00 2001 From: Rene Haas Date: Tue, 13 Sep 2022 01:03:51 +0200 Subject: [PATCH 08/17] improve entrypoint search --- packages/remix-dev/compiler/plugins/cssFilePlugin.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/remix-dev/compiler/plugins/cssFilePlugin.ts b/packages/remix-dev/compiler/plugins/cssFilePlugin.ts index 5898d45eeea..3a7da9f4fef 100644 --- a/packages/remix-dev/compiler/plugins/cssFilePlugin.ts +++ b/packages/remix-dev/compiler/plugins/cssFilePlugin.ts @@ -56,11 +56,9 @@ export function cssFilePlugin( ], }); - let keys = Object.keys(metafile.outputs); - let entry = keys.find((key) => { - let { entryPoint } = metafile.outputs[key]; - return !!entryPoint; - }); + let [entry] = Object.entries(metafile.outputs).find( + ([, meta]) => meta.entryPoint + )!; return { /** @@ -68,8 +66,8 @@ export function cssFilePlugin( * we would end up with two css files with different hashes in the output if we use the 'file' loader here. * Thats why we delete the generated css file, so the final css file in the output is the one created by the 'file' loader. */ - contents: await fse.readFile(entry!).then(async (contents) => { - await fse.remove(entry!); + contents: await fse.readFile(entry).then(async (contents) => { + await fse.remove(entry); return contents; }), loader: "file", From db271ed773eda3593696fa01824c28dabfbd2416 Mon Sep 17 00:00:00 2001 From: Rene Haas Date: Tue, 13 Sep 2022 14:56:14 +0200 Subject: [PATCH 09/17] better strategy for deletion of duplicates --- .../compiler/plugins/cssFilePlugin.ts | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/packages/remix-dev/compiler/plugins/cssFilePlugin.ts b/packages/remix-dev/compiler/plugins/cssFilePlugin.ts index 3a7da9f4fef..bb9178c0dc1 100644 --- a/packages/remix-dev/compiler/plugins/cssFilePlugin.ts +++ b/packages/remix-dev/compiler/plugins/cssFilePlugin.ts @@ -17,6 +17,7 @@ export function cssFilePlugin( async setup(build) { let buildOps = build.initialOptions; + let deleteDuplicates = new Set(); build.onLoad({ filter: /\.css$/ }, async (args) => { let { outfile, outdir, assetNames } = buildOps; @@ -60,19 +61,25 @@ export function cssFilePlugin( ([, meta]) => meta.entryPoint )!; + /** + * Because the css file was written to the output with the above build + * we would end up with two css files with different hashes in the output if we use the 'file' loader here. + * Thats why we delete the generated css file, so the final css file in the output is the one created by the 'file' loader. + */ + deleteDuplicates.add(entry); + return { - /** - * Because the css file was written to the output with the above build - * we would end up with two css files with different hashes in the output if we use the 'file' loader here. - * Thats why we delete the generated css file, so the final css file in the output is the one created by the 'file' loader. - */ - contents: await fse.readFile(entry).then(async (contents) => { - await fse.remove(entry); - return contents; - }), + contents: await fse.readFile(entry), loader: "file", }; }); + + build.onEnd(async () => { + await Promise.all( + Array.from(deleteDuplicates).map((file) => fse.remove(file)) + ); + deleteDuplicates.clear(); + }); }, }; } From 1f9ee9145be7c8b54e1cd01e9368169889f808aa Mon Sep 17 00:00:00 2001 From: Rene Haas Date: Wed, 14 Sep 2022 12:08:22 +0200 Subject: [PATCH 10/17] refine process and add watchFiles --- .../compiler/plugins/cssFilePlugin.ts | 51 +++++++++++-------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/packages/remix-dev/compiler/plugins/cssFilePlugin.ts b/packages/remix-dev/compiler/plugins/cssFilePlugin.ts index bb9178c0dc1..7108e137a41 100644 --- a/packages/remix-dev/compiler/plugins/cssFilePlugin.ts +++ b/packages/remix-dev/compiler/plugins/cssFilePlugin.ts @@ -17,17 +17,16 @@ export function cssFilePlugin( async setup(build) { let buildOps = build.initialOptions; - let deleteDuplicates = new Set(); build.onLoad({ filter: /\.css$/ }, async (args) => { let { outfile, outdir, assetNames } = buildOps; - let { metafile } = await esbuild.build({ + let { metafile, outputFiles } = await esbuild.build({ ...buildOps, minify: buildConfig.mode === BuildMode.Production, minifySyntax: true, metafile: true, - write: true, - sourcemap: false, // hash depends on sourcemap + write: false, + sourcemap: false, incremental: false, splitting: false, stdin: undefined, @@ -57,29 +56,41 @@ export function cssFilePlugin( ], }); - let [entry] = Object.entries(metafile.outputs).find( - ([, meta]) => meta.entryPoint + let { outputs } = metafile!; + let entry = Object.keys(outputs).find( + (out) => outputs[out].entryPoint )!; + let entryFile = outputFiles!.find((file) => file.path.endsWith(entry))!; + let outputFilesWithoutEntry = outputFiles!.filter( + (file) => file !== entryFile + ); - /** - * Because the css file was written to the output with the above build - * we would end up with two css files with different hashes in the output if we use the 'file' loader here. - * Thats why we delete the generated css file, so the final css file in the output is the one created by the 'file' loader. - */ - deleteDuplicates.add(entry); + // create directories for the assets + await Promise.all( + outputFilesWithoutEntry.map(({ path: filepath }) => + fse.promises.mkdir(path.dirname(filepath), { recursive: true }) + ) + ); + // write all assets + await Promise.all( + outputFilesWithoutEntry.map(({ path: filepath, contents }) => + fse.promises.writeFile(filepath, contents) + ) + ); return { - contents: await fse.readFile(entry), + contents: entryFile.contents, loader: "file", + // add all css assets to watchFiles + watchFiles: Object.values(outputs).reduce((arr, { inputs }) => { + let resolvedInputs = Object.keys(inputs).map((input) => + path.resolve(input) + ); + arr.push(...resolvedInputs); + return arr; + }, [] as string[]), }; }); - - build.onEnd(async () => { - await Promise.all( - Array.from(deleteDuplicates).map((file) => fse.remove(file)) - ); - deleteDuplicates.clear(); - }); }, }; } From 362250bcad6b7a82980c3498ed9c88ed9b9bec03 Mon Sep 17 00:00:00 2001 From: Rene Haas Date: Fri, 16 Sep 2022 10:24:40 +0200 Subject: [PATCH 11/17] pr review --- packages/remix-dev/compiler/plugins/cssFilePlugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/remix-dev/compiler/plugins/cssFilePlugin.ts b/packages/remix-dev/compiler/plugins/cssFilePlugin.ts index 7108e137a41..214142a670b 100644 --- a/packages/remix-dev/compiler/plugins/cssFilePlugin.ts +++ b/packages/remix-dev/compiler/plugins/cssFilePlugin.ts @@ -10,7 +10,7 @@ import type { BuildConfig } from "../../compiler"; * and exports the url of the css file as its default export. */ export function cssFilePlugin( - buildConfig: Partial + buildConfig: Pick, "mode"> ): esbuild.Plugin { return { name: "css-file", From 92473d02745d385e3989f581f944eeb83b87cb1c Mon Sep 17 00:00:00 2001 From: Rene Haas Date: Wed, 28 Sep 2022 23:46:31 +0200 Subject: [PATCH 12/17] add error and warnings --- .../remix-dev/compiler/plugins/cssFilePlugin.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/remix-dev/compiler/plugins/cssFilePlugin.ts b/packages/remix-dev/compiler/plugins/cssFilePlugin.ts index 214142a670b..4054e145706 100644 --- a/packages/remix-dev/compiler/plugins/cssFilePlugin.ts +++ b/packages/remix-dev/compiler/plugins/cssFilePlugin.ts @@ -20,7 +20,7 @@ export function cssFilePlugin( build.onLoad({ filter: /\.css$/ }, async (args) => { let { outfile, outdir, assetNames } = buildOps; - let { metafile, outputFiles } = await esbuild.build({ + let { metafile, outputFiles, warnings, errors } = await esbuild.build({ ...buildOps, minify: buildConfig.mode === BuildMode.Production, minifySyntax: true, @@ -38,6 +38,7 @@ export function cssFilePlugin( ...buildOps.loader, ".css": "css", }, + // this plugin treats absolute paths in 'url()' css rules as external to prevent breaking changes plugins: [ { name: "resolve-absolute", @@ -56,6 +57,10 @@ export function cssFilePlugin( ], }); + if (errors) { + return { errors }; + } + let { outputs } = metafile!; let entry = Object.keys(outputs).find( (out) => outputs[out].entryPoint @@ -65,16 +70,10 @@ export function cssFilePlugin( (file) => file !== entryFile ); - // create directories for the assets - await Promise.all( - outputFilesWithoutEntry.map(({ path: filepath }) => - fse.promises.mkdir(path.dirname(filepath), { recursive: true }) - ) - ); // write all assets await Promise.all( outputFilesWithoutEntry.map(({ path: filepath, contents }) => - fse.promises.writeFile(filepath, contents) + fse.outputFile(filepath, contents) ) ); @@ -89,6 +88,7 @@ export function cssFilePlugin( arr.push(...resolvedInputs); return arr; }, [] as string[]), + warnings, }; }); }, From a804d4eca0f2dfdddcdd2f6ce32138ff8797f76d Mon Sep 17 00:00:00 2001 From: Rene Haas Date: Fri, 30 Sep 2022 13:14:19 +0200 Subject: [PATCH 13/17] windows slashes fix --- packages/remix-dev/compiler/plugins/cssFilePlugin.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/remix-dev/compiler/plugins/cssFilePlugin.ts b/packages/remix-dev/compiler/plugins/cssFilePlugin.ts index 4054e145706..b466b0d9916 100644 --- a/packages/remix-dev/compiler/plugins/cssFilePlugin.ts +++ b/packages/remix-dev/compiler/plugins/cssFilePlugin.ts @@ -5,6 +5,11 @@ import esbuild from "esbuild"; import { BuildMode } from "../../build"; import type { BuildConfig } from "../../compiler"; +const isExtendedLengthPath = /^\\\\\?\\/; + +const normalizePathSlashes = (path: string) => + isExtendedLengthPath.test(path) ? path : path.replace(/\\/g, "/"); + /** * This plugin loads css files with the "css" loader (bundles and moves assets to assets directory) * and exports the url of the css file as its default export. @@ -65,7 +70,9 @@ export function cssFilePlugin( let entry = Object.keys(outputs).find( (out) => outputs[out].entryPoint )!; - let entryFile = outputFiles!.find((file) => file.path.endsWith(entry))!; + let entryFile = outputFiles!.find((file) => + normalizePathSlashes(file.path).endsWith(normalizePathSlashes(entry)) + )!; let outputFilesWithoutEntry = outputFiles!.filter( (file) => file !== entryFile ); From 85d5a9d0befcf61538df72bf987dda2d3780de82 Mon Sep 17 00:00:00 2001 From: Rene Haas Date: Mon, 10 Oct 2022 10:04:07 +0200 Subject: [PATCH 14/17] fix errors --- packages/remix-dev/compiler/plugins/cssFilePlugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/remix-dev/compiler/plugins/cssFilePlugin.ts b/packages/remix-dev/compiler/plugins/cssFilePlugin.ts index b466b0d9916..268ee43c3a6 100644 --- a/packages/remix-dev/compiler/plugins/cssFilePlugin.ts +++ b/packages/remix-dev/compiler/plugins/cssFilePlugin.ts @@ -62,7 +62,7 @@ export function cssFilePlugin( ], }); - if (errors) { + if (errors && errors.length) { return { errors }; } From 0d1344abee52595cb969cf11e451286dfb895aeb Mon Sep 17 00:00:00 2001 From: Logan McAnsh Date: Fri, 14 Oct 2022 14:16:59 -0400 Subject: [PATCH 15/17] chore: couple nit picks no "as" type casting, use invariant instead of enforcing a thing is defined Signed-off-by: Logan McAnsh --- .../compiler/plugins/cssFilePlugin.ts | 44 ++++++++++++------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/packages/remix-dev/compiler/plugins/cssFilePlugin.ts b/packages/remix-dev/compiler/plugins/cssFilePlugin.ts index 268ee43c3a6..b2262c5e6fd 100644 --- a/packages/remix-dev/compiler/plugins/cssFilePlugin.ts +++ b/packages/remix-dev/compiler/plugins/cssFilePlugin.ts @@ -4,11 +4,13 @@ import esbuild from "esbuild"; import { BuildMode } from "../../build"; import type { BuildConfig } from "../../compiler"; +import invariant from "../../invariant"; const isExtendedLengthPath = /^\\\\\?\\/; -const normalizePathSlashes = (path: string) => - isExtendedLengthPath.test(path) ? path : path.replace(/\\/g, "/"); +function normalizePathSlashes(p: string) { + return isExtendedLengthPath.test(p) ? p : p.replace(/\\/g, "/"); +} /** * This plugin loads css files with the "css" loader (bundles and moves assets to assets directory) @@ -66,14 +68,19 @@ export function cssFilePlugin( return { errors }; } - let { outputs } = metafile!; - let entry = Object.keys(outputs).find( - (out) => outputs[out].entryPoint - )!; - let entryFile = outputFiles!.find((file) => - normalizePathSlashes(file.path).endsWith(normalizePathSlashes(entry)) - )!; - let outputFilesWithoutEntry = outputFiles!.filter( + invariant(metafile, "metafile is missing"); + let { outputs } = metafile; + let entry = Object.keys(outputs).find((out) => outputs[out].entryPoint); + invariant(entry, "entry point not found"); + + let normalizedEntry = normalizePathSlashes(entry); + let entryFile = outputFiles.find((file) => { + return normalizePathSlashes(file.path).endsWith(normalizedEntry); + }); + + invariant(entryFile, "entry file not found"); + + let outputFilesWithoutEntry = outputFiles.filter( (file) => file !== entryFile ); @@ -88,13 +95,16 @@ export function cssFilePlugin( contents: entryFile.contents, loader: "file", // add all css assets to watchFiles - watchFiles: Object.values(outputs).reduce((arr, { inputs }) => { - let resolvedInputs = Object.keys(inputs).map((input) => - path.resolve(input) - ); - arr.push(...resolvedInputs); - return arr; - }, [] as string[]), + watchFiles: Object.values(outputs).reduce( + (arr, { inputs }) => { + let resolvedInputs = Object.keys(inputs).map((input) => { + return path.resolve(input); + }); + arr.push(...resolvedInputs); + return arr; + }, + [] + ), warnings, }; }); From c1f5c2b0694690026e43c7a36a5eaf961759b748 Mon Sep 17 00:00:00 2001 From: Logan McAnsh Date: Fri, 14 Oct 2022 15:25:21 -0400 Subject: [PATCH 16/17] test: add basic test confirming a font was copied Signed-off-by: Logan McAnsh --- integration/compiler-test.ts | 44 ++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/integration/compiler-test.ts b/integration/compiler-test.ts index b33e5ddc6bd..4e4cdb5710a 100644 --- a/integration/compiler-test.ts +++ b/integration/compiler-test.ts @@ -1,5 +1,5 @@ import path from "path"; -import fs from "fs/promises"; +import fse from "fs-extra"; import { test, expect } from "@playwright/test"; import { PassThrough } from "stream"; @@ -9,6 +9,7 @@ import { js, json, createFixtureProject, + css, } from "./helpers/create-fixture"; import type { Fixture, AppFixture } from "./helpers/create-fixture"; import { PlaywrightFixture } from "./helpers/playwright-fixture"; @@ -89,6 +90,18 @@ test.describe("compiler", () => { return
{submodule()}
; } `, + "app/routes/css.jsx": js` + import stylesUrl from "@org/css/index.css"; + + export function links() { + return [{ rel: "stylesheet", href: stylesUrl }] + } + + export default function PackageWithSubModule() { + return
{submodule()}
; + } + `, + "remix.config.js": js` let { getDependenciesToBundle } = require("@remix-run/dev"); module.exports = { @@ -156,6 +169,22 @@ test.describe("compiler", () => { return "package-with-submodule"; } `, + "node_modules/@org/css/package.json": json({ + name: "@org/css", + version: "1.0.0", + main: "index.css", + }), + "node_modules/@org/css/font.woff2": "font", + "node_modules/@org/css/index.css": css` + body { + background: red; + } + + @font-face { + font-family: "MyFont"; + src: url("./font.woff2"); + } + `, }, }); @@ -266,6 +295,17 @@ test.describe("compiler", () => { ); }); + test("copies imports in css files to assetsBuildDirectory", async () => { + let buildDir = path.join(fixture.projectDir, "public", "build", "_assets"); + let files = await fse.readdir(buildDir); + expect(files).toHaveLength(2); + + let cssFile = files.find((file) => file.match(/index-[a-z0-9]{8}\.css/i)); + let fontFile = files.find((file) => file.match(/font-[a-z0-9]{8}\.woff2/i)); + expect(cssFile).toBeTruthy(); + expect(fontFile).toBeTruthy(); + }); + // TODO: remove this when we get rid of that feature. test("magic imports still works", async () => { let magicExportsForNode = [ @@ -314,7 +354,7 @@ test.describe("compiler", () => { "useSubmit", "useTransition", ]; - let magicRemix = await fs.readFile( + let magicRemix = await fse.readFile( path.resolve(fixture.projectDir, "node_modules/remix/dist/index.js"), "utf8" ); From f77eeca27d3a05f8c3f0ee10cc22b853a744ccd6 Mon Sep 17 00:00:00 2001 From: Logan McAnsh Date: Fri, 14 Oct 2022 16:38:15 -0400 Subject: [PATCH 17/17] Create large-colts-drop.md --- .changeset/large-colts-drop.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changeset/large-colts-drop.md diff --git a/.changeset/large-colts-drop.md b/.changeset/large-colts-drop.md new file mode 100644 index 00000000000..87743524060 --- /dev/null +++ b/.changeset/large-colts-drop.md @@ -0,0 +1,8 @@ +--- +"remix": patch +"@remix-run/dev": patch +--- + +add CSS plugin to `esbuild` so that any assets in css files are also copied (and hashed) to the `assetsBuildDirectory` + +currently if you import a css file that has `background: url('./relative.png');` the `relative.png` file is not copied to the build directory, which is a problem when dealing with npm packages that have css files with font files in them like fontsource