-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Upgrade docs site to Bridgetown 1.3.1 and esbuild
- Loading branch information
1 parent
cbe2e02
commit d0f1d42
Showing
10 changed files
with
1,397 additions
and
4,097 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 |
---|---|---|
@@ -1,6 +1,6 @@ | ||
source "https://rubygems.org" | ||
git_source(:github) { |repo| "https://github.com/#{repo}.git" } | ||
|
||
gem "bridgetown", "1.0.0.alpha9" | ||
gem "bridgetown", "~> 1.3.1" | ||
|
||
gem "puma", "~> 5.5" | ||
gem "puma", "~> 6.3" |
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,351 @@ | ||
// This file is created and managed by Bridgetown. | ||
// Instead of editing this file, add your overrides to `esbuild.config.js` | ||
// | ||
// To update this file to the latest version provided by Bridgetown, | ||
// run `bridgetown esbuild update`. Any changes to this file will be overwritten | ||
// when an update is applied hence we strongly recommend adding overrides to | ||
// `esbuild.config.js` instead of editing this file. | ||
// | ||
// Shipped with Bridgetown v1.3.1 | ||
|
||
// DO NOT MANUALLY EDIT THIS CONFIGURATION: | ||
const autogeneratedBridgetownConfig = { | ||
"source": "src", | ||
"destination": "output", | ||
"componentsDir": "_components", | ||
"islandsDir": "_islands" | ||
} | ||
|
||
const path = require("path") | ||
const fsLib = require("fs") | ||
const fs = fsLib.promises | ||
const { pathToFileURL, fileURLToPath } = require("url") | ||
const glob = require("glob") | ||
const postcss = require("postcss") | ||
const postCssImport = require("postcss-import") | ||
const readCache = require("read-cache") | ||
|
||
// Detect if an NPM package is available | ||
const moduleAvailable = name => { | ||
try { | ||
require.resolve(name) | ||
return true | ||
} catch (e) { } | ||
return false | ||
} | ||
|
||
// Generate a Source Map URL (used by the Sass plugin) | ||
const generateSourceMappingURL = sourceMap => { | ||
const data = Buffer.from(JSON.stringify(sourceMap), "utf-8").toString("base64") | ||
return `/*# sourceMappingURL=data:application/json;charset=utf-8;base64,${data} */` | ||
} | ||
|
||
// Import Sass if available | ||
let sass | ||
if (moduleAvailable("sass")) { | ||
sass = require("sass") | ||
} | ||
|
||
// Glob plugin derived from: | ||
// https://github.com/thomaschaaf/esbuild-plugin-import-glob | ||
// https://github.com/xiaohui-zhangxh/jsbundling-rails/commit/b15025dcc20f664b2b0eb238915991afdbc7cb58 | ||
const importGlobPlugin = (options, bridgetownConfig) => ({ | ||
name: "import-glob", | ||
setup: (build) => { | ||
build.onResolve({ filter: /\*/ }, async (args) => { | ||
if (args.resolveDir === "") { | ||
return; // Ignore unresolvable paths | ||
} | ||
|
||
const adjustedPath = args.path | ||
.replace(/^\$components\/\*\*/, `../../${bridgetownConfig.source}/${bridgetownConfig.componentsDir}/**`) | ||
.replace(/^bridgetownComponents\//, `../../${bridgetownConfig.source}/${bridgetownConfig.componentsDir}/`) // legacy | ||
|
||
return { | ||
path: adjustedPath, | ||
namespace: "import-glob", | ||
pluginData: { | ||
path: adjustedPath, | ||
resolveDir: args.resolveDir, | ||
}, | ||
} | ||
}) | ||
|
||
build.onLoad({ filter: /.*/, namespace: "import-glob" }, async (args) => { | ||
const files = glob.sync(args.pluginData.path, { | ||
cwd: args.pluginData.resolveDir, | ||
}) | ||
.filter(module => options.excludeFilter ? !options.excludeFilter.test(module) : true) | ||
.sort() | ||
.map(module => module.replace(`../../${bridgetownConfig.source}/${bridgetownConfig.componentsDir}/`, "$components/")) | ||
.map(module => module.match(/^[a-zA-Z0-9]/) ? `./${module}` : module) | ||
|
||
const importerCode = ` | ||
${files | ||
.map((module, index) => `import * as module${index} from '${module}'`) | ||
.join(';')} | ||
const modules = {${files | ||
.map((module, index) => ` | ||
"${module.replace("$components/", "")}": module${index},`) | ||
.join("")} | ||
}; | ||
export default modules; | ||
` | ||
|
||
return { contents: importerCode, resolveDir: args.pluginData.resolveDir } | ||
}) | ||
}, | ||
}) | ||
|
||
// Plugin for PostCSS | ||
const importPostCssPlugin = (options, configuration) => ({ | ||
name: "postcss", | ||
async setup(build) { | ||
// Process .css files with PostCSS | ||
build.onLoad({ filter: (configuration.filter || /\.css$/) }, async (args) => { | ||
if (args.path.endsWith(".lit.css")) return; | ||
|
||
const additionalFilePaths = [] | ||
const css = await fs.readFile(args.path, "utf8") | ||
|
||
// Configure import plugin so PostCSS can properly resolve `@import`ed CSS files | ||
const importPlugin = postCssImport({ | ||
filter: itemPath => !itemPath.startsWith("/"), // ensure it doesn't try to import source-relative paths | ||
load: async filename => { | ||
let contents = await readCache(filename, "utf-8") | ||
const filedir = path.dirname(filename) | ||
// We'll want to track any imports later when in watch mode: | ||
additionalFilePaths.push(filename) | ||
|
||
// We need to transform `url(...)` in imported CSS so the filepaths are properly | ||
// relative to the entrypoint. Seems icky to have to hack this! C'est la vie... | ||
contents = contents.replace(/url\(['"]?\.\/(.*?)['"]?\)/g, (_match, p1) => { | ||
const relpath = path.relative(args.path, path.resolve(filedir, p1)).replace(/^\.\.\//, "") | ||
return `url("${relpath}")` | ||
}) | ||
return contents | ||
} | ||
}) | ||
|
||
// Process the file through PostCSS | ||
const result = await postcss([importPlugin, ...options.plugins]).process(css, { | ||
map: true, | ||
...options.options, | ||
from: args.path, | ||
}); | ||
|
||
return { | ||
contents: result.css, | ||
loader: "css", | ||
watchFiles: [args.path, ...additionalFilePaths], | ||
} | ||
}) | ||
}, | ||
}) | ||
|
||
// Plugin for Sass | ||
const sassPlugin = (options) => ({ | ||
name: "sass", | ||
async setup(build) { | ||
// Process .scss and .sass files with Sass | ||
build.onLoad({ filter: /\.(sass|scss)$/ }, async (args) => { | ||
if (!sass) { | ||
console.error("error: Sass is not installed. Try running `yarn add sass` and then building again.") | ||
return | ||
} | ||
|
||
const modulesFolder = pathToFileURL("node_modules/") | ||
|
||
const localOptions = { | ||
importers: [{ | ||
// An importer that redirects relative URLs starting with "~" to | ||
// `node_modules`. | ||
findFileUrl(url) { | ||
if (!url.startsWith('~')) return null | ||
return new URL(url.substring(1), modulesFolder) | ||
} | ||
}], | ||
sourceMap: true, | ||
...options | ||
} | ||
const result = sass.compile(args.path, localOptions) | ||
|
||
const watchPaths = result.loadedUrls | ||
.filter((x) => x.protocol === "file:" && !x.pathname.startsWith(modulesFolder.pathname)) | ||
.map((x) => x.pathname) | ||
|
||
let cssOutput = result.css.toString() | ||
|
||
if (result.sourceMap) { | ||
const basedir = process.cwd() | ||
const sourceMap = result.sourceMap | ||
|
||
const promises = sourceMap.sources.map(async source => { | ||
const sourceFile = await fs.readFile(fileURLToPath(source), "utf8") | ||
return sourceFile | ||
}) | ||
sourceMap.sourcesContent = await Promise.all(promises) | ||
|
||
sourceMap.sources = sourceMap.sources.map(source => { | ||
return path.relative(basedir, fileURLToPath(source)) | ||
}) | ||
|
||
cssOutput += '\n' + generateSourceMappingURL(sourceMap) | ||
} | ||
|
||
return { | ||
contents: cssOutput, | ||
loader: "css", | ||
watchFiles: [args.path, ...watchPaths], | ||
} | ||
}) | ||
}, | ||
}) | ||
|
||
// Set up defaults and generate frontend bundling manifest file | ||
const bridgetownPreset = (bridgetownConfig) => ({ | ||
name: "bridgetownPreset", | ||
async setup(build) { | ||
// Ensure any imports anywhere starting with `/` are left verbatim | ||
// so they can be used in-browser for actual `src` repo files | ||
build.onResolve({ filter: /^\// }, args => { | ||
return { path: args.path, external: true } | ||
}) | ||
|
||
build.onStart(() => { | ||
console.log("esbuild: frontend bundling started...") | ||
}) | ||
|
||
// Generate the final output manifest | ||
build.onEnd(async (result) => { | ||
if (!result.metafile) { | ||
console.warn("esbuild: build process error, cannot write manifest") | ||
return | ||
} | ||
|
||
const manifest = {} | ||
const entrypoints = [] | ||
|
||
// Clean up entrypoint naming | ||
const stripPrefix = (str) => str | ||
.replace(/^frontend\//, "") | ||
.replace(RegExp(String.raw`^${bridgetownConfig.source}\/${bridgetownConfig.islandsDir}\/`), "islands/") | ||
|
||
// For calculating the file size of bundle output | ||
const fileSize = (path) => { | ||
const { size } = fsLib.statSync(path) | ||
const i = Math.floor(Math.log(size) / Math.log(1024)) | ||
return (size / Math.pow(1024, i)).toFixed(2) * 1 + ['B', 'KB', 'MB', 'GB', 'TB'][i] | ||
} | ||
|
||
const pathShortener = new RegExp(String.raw`^${bridgetownConfig.destination}\/_bridgetown\/static\/`, "g") | ||
|
||
// Let's loop through all the various outputs | ||
for (const key in result.metafile.outputs) { | ||
const value = result.metafile.outputs[key] | ||
const inputs = Object.keys(value.inputs) | ||
const outputPath = key.replace(pathShortener, "") | ||
|
||
if (value.entryPoint) { | ||
// We have an entrypoint! | ||
manifest[stripPrefix(value.entryPoint)] = outputPath | ||
entrypoints.push([outputPath, fileSize(key)]) | ||
} else if (key.match(/index(\.js)?\.[^-.]*\.css/) && inputs.find(item => item.match(/frontend.*\.(s?css|sass)$/))) { | ||
// Special treatment for index.css | ||
const input = inputs.find(item => item.match(/frontend.*\.(s?css|sass)$/)) | ||
manifest[stripPrefix(input)] = outputPath | ||
entrypoints.push([outputPath, fileSize(key)]) | ||
} else if (inputs.length > 0) { | ||
// Naive implementation, we'll just grab the first input and hope it's accurate | ||
manifest[stripPrefix(inputs[0])] = outputPath | ||
} | ||
} | ||
|
||
const manifestFolder = path.join(process.cwd(), ".bridgetown-cache", "frontend-bundling") | ||
await fs.mkdir(manifestFolder, { recursive: true }) | ||
await fs.writeFile(path.join(manifestFolder, "manifest.json"), JSON.stringify(manifest)) | ||
|
||
console.log("esbuild: frontend bundling complete!") | ||
console.log("esbuild: entrypoints processed:") | ||
entrypoints.forEach(entrypoint => { | ||
const [entrypointName, entrypointSize] = entrypoint | ||
console.log(` - ${entrypointName}: ${entrypointSize}`) | ||
}) | ||
}) | ||
} | ||
}) | ||
|
||
const bridgetownConfigured = (bridgetownConfig, outputFolder) => { | ||
bridgetownConfig = {...autogeneratedBridgetownConfig, ...bridgetownConfig} | ||
if (outputFolder) bridgetownConfig.destination = outputFolder | ||
|
||
return bridgetownConfig | ||
} | ||
|
||
// Load the PostCSS config from postcss.config.js or whatever else is a supported location/format | ||
const postcssrc = require("postcss-load-config") | ||
|
||
module.exports = async (esbuildOptions, ...args) => { | ||
let outputFolder; | ||
if (typeof esbuildOptions === "string") { // legacy syntax where first argument is output folder | ||
outputFolder = esbuildOptions | ||
esbuildOptions = args[0] | ||
} | ||
const bridgetownConfig = bridgetownConfigured(esbuildOptions.bridgetownConfig, outputFolder) | ||
|
||
esbuildOptions.plugins = esbuildOptions.plugins || [] | ||
// Add the PostCSS & glob plugins to the top of the plugin stack | ||
const postCssConfig = await postcssrc() | ||
esbuildOptions.plugins.unshift(importPostCssPlugin(postCssConfig, esbuildOptions.postCssPluginConfig || {})) | ||
if (esbuildOptions.postCssPluginConfig) delete esbuildOptions.postCssPluginConfig | ||
// Add the Glob plugin | ||
esbuildOptions.plugins.unshift(importGlobPlugin(esbuildOptions.globOptions || {}, bridgetownConfig)) | ||
if (esbuildOptions.globOptions) delete esbuildOptions.globOptions | ||
// Add the Sass plugin | ||
esbuildOptions.plugins.push(sassPlugin(esbuildOptions.sassOptions || {})) | ||
if (esbuildOptions.sassOptions) delete esbuildOptions.sassOptions | ||
// Add the Bridgetown preset | ||
esbuildOptions.plugins.push(bridgetownPreset(bridgetownConfig)) | ||
if (esbuildOptions.bridgetownConfig) delete esbuildOptions.bridgetownConfig | ||
|
||
const esbuild = require("esbuild") | ||
const entryPoints = esbuildOptions.entryPoints || ["./frontend/javascript/index.js"] | ||
if (esbuildOptions.entryPoints) delete esbuildOptions.entryPoints | ||
|
||
const islands = glob.sync(`./${bridgetownConfig.source}/${bridgetownConfig.islandsDir}/*.{js,js.rb}`).map(item => `./${item}`) | ||
|
||
esbuild.context({ | ||
bundle: true, | ||
loader: { | ||
".jpg": "file", | ||
".png": "file", | ||
".gif": "file", | ||
".svg": "file", | ||
".woff": "file", | ||
".woff2": "file", | ||
".ttf": "file", | ||
".eot": "file", | ||
}, | ||
resolveExtensions: [".tsx", ".ts", ".jsx", ".js", ".css", ".scss", ".sass", ".json", ".js.rb"], | ||
minify: process.argv.includes("--minify"), | ||
sourcemap: true, | ||
target: "es2020", | ||
entryPoints: [...entryPoints, ...islands], | ||
entryNames: "[dir]/[name].[hash]", | ||
outdir: path.join(process.cwd(), `${bridgetownConfig.destination}/_bridgetown/static`), | ||
publicPath: "/_bridgetown/static", | ||
metafile: true, | ||
...esbuildOptions, | ||
}).then(context => { | ||
if (process.argv.includes("--watch")) { | ||
// Enable watch mode | ||
context.watch() | ||
} else { | ||
// Build once and exit if not in watch mode | ||
context.rebuild().then(result => { | ||
context.dispose() | ||
}) | ||
} | ||
process.on('SIGINT', () => process.exit()) | ||
}).catch(() => process.exit(1)) | ||
} |
Oops, something went wrong.