including-modules
This commit is contained in:
+118
@@ -0,0 +1,118 @@
|
||||
import path from 'path';
|
||||
import { bold, cyan } from '../../../../lib/picocolors';
|
||||
import loaderUtils from 'next/dist/compiled/loader-utils3';
|
||||
import postcssNextFontPlugin from './postcss-next-font';
|
||||
import { promisify } from 'util';
|
||||
export default async function nextFontLoader() {
|
||||
const nextFontLoaderSpan = this.currentTraceSpan.traceChild('next-font-loader');
|
||||
return nextFontLoaderSpan.traceAsyncFn(async ()=>{
|
||||
const callback = this.async();
|
||||
/**
|
||||
* The next-swc plugin next-transform-font turns font function calls into CSS imports.
|
||||
* At the end of the import, it adds the call arguments and some additional data as a resourceQuery.
|
||||
* e.g:
|
||||
* const inter = Inter({ subset: ['latin'] })
|
||||
* ->
|
||||
* import inter from 'next/font/google/target.css?{"import":"Inter","subsets":["latin"]}'
|
||||
*
|
||||
* Here we parse the resourceQuery to get the font function name, call arguments, and the path to the file that called the font function.
|
||||
*/ const { path: relativeFilePathFromRoot, import: functionName, arguments: data, variableName } = JSON.parse(this.resourceQuery.slice(1));
|
||||
// Throw error if @next/font is used in _document.js
|
||||
if (/pages[\\/]_document\./.test(relativeFilePathFromRoot)) {
|
||||
const err = Object.defineProperty(new Error(`${bold('Cannot')} be used within ${cyan('pages/_document.js')}.`), "__NEXT_ERROR_CODE", {
|
||||
value: "E135",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
err.name = 'NextFontError';
|
||||
if (process.env.NEXT_RSPACK) {
|
||||
// Rspack uses miette for error formatting, which automatically includes stack
|
||||
// traces in the error message. To avoid showing redundant stack information
|
||||
// in the final error output, we clear the stack property.
|
||||
err.stack = undefined;
|
||||
}
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
const { isDev, isServer, assetPrefix, fontLoaderPath, postcss: getPostcss } = this.getOptions();
|
||||
if (assetPrefix && !/^\/|https?:\/\//.test(assetPrefix)) {
|
||||
const err = Object.defineProperty(new Error('assetPrefix must start with a leading slash or be an absolute URL(http:// or https://)'), "__NEXT_ERROR_CODE", {
|
||||
value: "E72",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
err.name = 'NextFontError';
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Emit font files to .next/static/media as [hash].[ext].
|
||||
*
|
||||
* If the font should be preloaded, add .p to the filename: [hash].p.[ext]
|
||||
* NextFontManifestPlugin adds these files to the next/font manifest.
|
||||
*
|
||||
* If the font is using a size-adjust fallback font, add -s to the filename: [hash]-s.[ext]
|
||||
* NextFontManifestPlugin uses this to see if fallback fonts are being used.
|
||||
* This is used to collect stats on fallback fonts usage by the Google Aurora team.
|
||||
*/ const emitFontFile = (content, ext, preload, isUsingSizeAdjust)=>{
|
||||
const opts = {
|
||||
context: this.rootContext,
|
||||
content
|
||||
};
|
||||
const interpolatedName = loaderUtils.interpolateName(this, `static/media/[hash]${isUsingSizeAdjust ? '-s' : ''}${preload ? '.p' : ''}.${ext}`, opts);
|
||||
const outputPath = `${assetPrefix}/_next/${interpolatedName}`;
|
||||
// Only the client emits the font file
|
||||
if (!isServer) {
|
||||
this.emitFile(interpolatedName, content, null);
|
||||
}
|
||||
// But both the server and client must get the resulting path
|
||||
return outputPath;
|
||||
};
|
||||
try {
|
||||
// Import the font loader function from either next/font/local or next/font/google
|
||||
// The font loader function emits font files and returns @font-faces and fallback font metrics
|
||||
const fontLoader = require(fontLoaderPath).default;
|
||||
let { css, fallbackFonts, adjustFontFallback, weight, style, variable } = await nextFontLoaderSpan.traceChild('font-loader').traceAsyncFn(()=>fontLoader({
|
||||
functionName,
|
||||
variableName,
|
||||
data,
|
||||
emitFontFile,
|
||||
resolve: (src)=>promisify(this.resolve)(path.dirname(path.join(this.rootContext, relativeFilePathFromRoot)), src.startsWith('.') ? src : `./${src}`),
|
||||
isDev,
|
||||
isServer,
|
||||
loaderContext: this
|
||||
}));
|
||||
const { postcss } = await getPostcss();
|
||||
// Exports will be exported as is from css-loader instead of a CSS module export
|
||||
const exports = [];
|
||||
// Generate a hash from the CSS content. Used to generate classnames
|
||||
const fontFamilyHash = loaderUtils.getHashDigest(Buffer.from(css), 'sha1', 'hex', 6);
|
||||
// Add CSS classes, exports and make the font-family locally scoped by turning it unguessable
|
||||
const result = await nextFontLoaderSpan.traceChild('postcss').traceAsyncFn(()=>postcss(postcssNextFontPlugin({
|
||||
exports,
|
||||
fallbackFonts,
|
||||
weight,
|
||||
style,
|
||||
adjustFontFallback,
|
||||
variable
|
||||
})).process(css, {
|
||||
from: undefined
|
||||
}));
|
||||
const ast = {
|
||||
type: 'postcss',
|
||||
version: result.processor.version,
|
||||
root: result.root
|
||||
};
|
||||
// Return the resulting CSS and send the postcss ast, font exports and the hash to the css-loader in the meta argument.
|
||||
callback(null, result.css, null, {
|
||||
exports,
|
||||
ast,
|
||||
fontFamilyHash
|
||||
});
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//# sourceMappingURL=index.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
Generated
Vendored
+154
@@ -0,0 +1,154 @@
|
||||
import postcss from 'postcss';
|
||||
/**
|
||||
* The next/font postcss plugin receives the @font-face declarations returned from the next/font loaders.
|
||||
*
|
||||
* It hashes the font-family name to make it unguessable, it shouldn't be globally accessible.
|
||||
* If it were global, we wouldn't be able to tell which pages are using which fonts when generating preload tags.
|
||||
*
|
||||
* If the font loader returned fallback metrics, generate a fallback @font-face.
|
||||
*
|
||||
* If the font loader returned a variable name, add a CSS class that declares a variable containing the font and fallback fonts.
|
||||
*
|
||||
* Lastly, it adds the font-family to the exports object.
|
||||
* This enables you to access the actual font-family name, not just through the CSS class.
|
||||
* e.g:
|
||||
* const inter = Inter({ subsets: ['latin'] })
|
||||
* inter.style.fontFamily // => '__Inter_123456'
|
||||
*/ const postcssNextFontPlugin = ({ exports, fallbackFonts = [], adjustFontFallback, variable, weight, style })=>{
|
||||
return {
|
||||
postcssPlugin: 'postcss-next-font',
|
||||
Once (root) {
|
||||
let fontFamily;
|
||||
const normalizeFamily = (family)=>{
|
||||
return family.replace(/['"]/g, '');
|
||||
};
|
||||
const formatFamily = (family)=>{
|
||||
return `'${family}'`;
|
||||
};
|
||||
// Hash font-family names
|
||||
for (const node of root.nodes){
|
||||
if (node.type === 'atrule' && node.name === 'font-face') {
|
||||
const familyNode = node.nodes.find((decl)=>decl.prop === 'font-family');
|
||||
if (!familyNode) {
|
||||
continue;
|
||||
}
|
||||
if (!fontFamily) {
|
||||
fontFamily = normalizeFamily(familyNode.value);
|
||||
}
|
||||
familyNode.value = formatFamily(fontFamily);
|
||||
}
|
||||
}
|
||||
if (!fontFamily) {
|
||||
throw Object.defineProperty(new Error("Font loaders must return one or more @font-face's"), "__NEXT_ERROR_CODE", {
|
||||
value: "E428",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
// Add fallback @font-face with the provided override values
|
||||
let adjustFontFallbackFamily;
|
||||
if (adjustFontFallback) {
|
||||
adjustFontFallbackFamily = formatFamily(`${fontFamily} Fallback`);
|
||||
const fallbackFontFace = postcss.atRule({
|
||||
name: 'font-face'
|
||||
});
|
||||
const { fallbackFont, ascentOverride, descentOverride, lineGapOverride, sizeAdjust } = adjustFontFallback;
|
||||
fallbackFontFace.nodes = [
|
||||
new postcss.Declaration({
|
||||
prop: 'font-family',
|
||||
value: adjustFontFallbackFamily
|
||||
}),
|
||||
new postcss.Declaration({
|
||||
prop: 'src',
|
||||
value: `local("${fallbackFont}")`
|
||||
}),
|
||||
...ascentOverride ? [
|
||||
new postcss.Declaration({
|
||||
prop: 'ascent-override',
|
||||
value: ascentOverride
|
||||
})
|
||||
] : [],
|
||||
...descentOverride ? [
|
||||
new postcss.Declaration({
|
||||
prop: 'descent-override',
|
||||
value: descentOverride
|
||||
})
|
||||
] : [],
|
||||
...lineGapOverride ? [
|
||||
new postcss.Declaration({
|
||||
prop: 'line-gap-override',
|
||||
value: lineGapOverride
|
||||
})
|
||||
] : [],
|
||||
...sizeAdjust ? [
|
||||
new postcss.Declaration({
|
||||
prop: 'size-adjust',
|
||||
value: sizeAdjust
|
||||
})
|
||||
] : []
|
||||
];
|
||||
root.nodes.push(fallbackFontFace);
|
||||
}
|
||||
// Variable fonts can define ranges of values
|
||||
const isRange = (value)=>value.trim().includes(' ');
|
||||
// Format the font families to be used in the CSS
|
||||
const formattedFontFamilies = [
|
||||
formatFamily(fontFamily),
|
||||
...adjustFontFallbackFamily ? [
|
||||
adjustFontFallbackFamily
|
||||
] : [],
|
||||
...fallbackFonts
|
||||
].join(', ');
|
||||
// Add class with family, weight and style
|
||||
const classRule = new postcss.Rule({
|
||||
selector: '.className'
|
||||
});
|
||||
classRule.nodes = [
|
||||
new postcss.Declaration({
|
||||
prop: 'font-family',
|
||||
value: formattedFontFamilies
|
||||
}),
|
||||
// If the font only has one weight or style, we can set it on the class
|
||||
...weight && !isRange(weight) ? [
|
||||
new postcss.Declaration({
|
||||
prop: 'font-weight',
|
||||
value: weight
|
||||
})
|
||||
] : [],
|
||||
...style && !isRange(style) ? [
|
||||
new postcss.Declaration({
|
||||
prop: 'font-style',
|
||||
value: style
|
||||
})
|
||||
] : []
|
||||
];
|
||||
root.nodes.push(classRule);
|
||||
// Add CSS class that defines a variable with the font families
|
||||
if (variable) {
|
||||
const varialbeRule = new postcss.Rule({
|
||||
selector: '.variable'
|
||||
});
|
||||
varialbeRule.nodes = [
|
||||
new postcss.Declaration({
|
||||
prop: variable,
|
||||
value: formattedFontFamilies
|
||||
})
|
||||
];
|
||||
root.nodes.push(varialbeRule);
|
||||
}
|
||||
// Export @font-face values as is
|
||||
exports.push({
|
||||
name: 'style',
|
||||
value: {
|
||||
fontFamily: formattedFontFamilies,
|
||||
fontWeight: !Number.isNaN(Number(weight)) ? Number(weight) : undefined,
|
||||
fontStyle: style && !isRange(style) ? style : undefined
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
postcssNextFontPlugin.postcss = true;
|
||||
export default postcssNextFontPlugin;
|
||||
|
||||
//# sourceMappingURL=postcss-next-font.js.map
|
||||
Generated
Vendored
+1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user