-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(remix-dev/compiler): add CSS plugin to
esbuild
(#4130)
* create and add css file plugin * Update contributors.yml * absolute url paths are considered external * add correct sourcemap and minify behavior * improve code * add comment why sourcemap false * improve code and output * improve entrypoint search * better strategy for deletion of duplicates * refine process and add watchFiles * pr review * add error and warnings * windows slashes fix * fix errors * chore: couple nit picks no "as" type casting, use invariant instead of enforcing a thing is defined Signed-off-by: Logan McAnsh <logan@mcan.sh> * test: add basic test confirming a font was copied Signed-off-by: Logan McAnsh <logan@mcan.sh> * Create large-colts-drop.md Signed-off-by: Logan McAnsh <logan@mcan.sh> Co-authored-by: Logan McAnsh <logan@mcan.sh>
- Loading branch information
Showing
5 changed files
with
168 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -230,6 +230,7 @@ | |
- kilian | ||
- kiliman | ||
- kimdontdoit | ||
- KingSora | ||
- klauspaiva | ||
- knowler | ||
- konradkalemba | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import * as path from "path"; | ||
import * as fse from "fs-extra"; | ||
import esbuild from "esbuild"; | ||
|
||
import { BuildMode } from "../../build"; | ||
import type { BuildConfig } from "../../compiler"; | ||
import invariant from "../../invariant"; | ||
|
||
const isExtendedLengthPath = /^\\\\\?\\/; | ||
|
||
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) | ||
* and exports the url of the css file as its default export. | ||
*/ | ||
export function cssFilePlugin( | ||
buildConfig: Pick<Partial<BuildConfig>, "mode"> | ||
): esbuild.Plugin { | ||
return { | ||
name: "css-file", | ||
|
||
async setup(build) { | ||
let buildOps = build.initialOptions; | ||
|
||
build.onLoad({ filter: /\.css$/ }, async (args) => { | ||
let { outfile, outdir, assetNames } = buildOps; | ||
let { metafile, outputFiles, warnings, errors } = await esbuild.build({ | ||
...buildOps, | ||
minify: buildConfig.mode === BuildMode.Production, | ||
minifySyntax: true, | ||
metafile: true, | ||
write: false, | ||
sourcemap: false, | ||
incremental: false, | ||
splitting: false, | ||
stdin: undefined, | ||
outfile: undefined, | ||
outdir: outfile ? path.dirname(outfile) : outdir, | ||
entryNames: assetNames, | ||
entryPoints: [args.path], | ||
loader: { | ||
...buildOps.loader, | ||
".css": "css", | ||
}, | ||
// this plugin treats absolute paths in 'url()' css rules as external to prevent breaking changes | ||
plugins: [ | ||
{ | ||
name: "resolve-absolute", | ||
async setup(build) { | ||
build.onResolve({ filter: /.*/ }, async (args) => { | ||
let { kind, path: resolvePath } = args; | ||
if (kind === "url-token" && path.isAbsolute(resolvePath)) { | ||
return { | ||
path: resolvePath, | ||
external: true, | ||
}; | ||
} | ||
}); | ||
}, | ||
}, | ||
], | ||
}); | ||
|
||
if (errors && errors.length) { | ||
return { errors }; | ||
} | ||
|
||
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 | ||
); | ||
|
||
// write all assets | ||
await Promise.all( | ||
outputFilesWithoutEntry.map(({ path: filepath, contents }) => | ||
fse.outputFile(filepath, contents) | ||
) | ||
); | ||
|
||
return { | ||
contents: entryFile.contents, | ||
loader: "file", | ||
// add all css assets to watchFiles | ||
watchFiles: Object.values(outputs).reduce<string[]>( | ||
(arr, { inputs }) => { | ||
let resolvedInputs = Object.keys(inputs).map((input) => { | ||
return path.resolve(input); | ||
}); | ||
arr.push(...resolvedInputs); | ||
return arr; | ||
}, | ||
[] | ||
), | ||
warnings, | ||
}; | ||
}); | ||
}, | ||
}; | ||
} |