next
Version:
The React Framework
214 lines (213 loc) • 10.5 kB
JavaScript
Object.defineProperty(exports, "__esModule", {
value: true
});
var _webpack = require("next/dist/compiled/webpack/webpack");
var _fontUtils = require("../../../server/font-utils");
var _postcss = _interopRequireDefault(require("postcss"));
var _cssnanoSimple = _interopRequireDefault(require("next/dist/compiled/cssnano-simple"));
var _constants = require("../../../shared/lib/constants");
var Log = _interopRequireWildcard(require("../../output/log"));
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
function _interopRequireWildcard(obj) {
if (obj && obj.__esModule) {
return obj;
} else {
var newObj = {};
if (obj != null) {
for(var key in obj){
if (Object.prototype.hasOwnProperty.call(obj, key)) {
var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {};
if (desc.get || desc.set) {
Object.defineProperty(newObj, key, desc);
} else {
newObj[key] = obj[key];
}
}
}
}
newObj.default = obj;
return newObj;
}
}
function minifyCss(css) {
return (0, _postcss).default([
(0, _cssnanoSimple).default({
excludeAll: true,
discardComments: true,
normalizeWhitespace: {
exclude: false
}
}, _postcss.default),
]).process(css, {
from: undefined
}).then((res)=>res.css
);
}
class FontStylesheetGatheringPlugin {
constructor({ isLikeServerless }){
this.gatheredStylesheets = [];
this.manifestContent = [];
this.parserHandler = (factory)=>{
const JS_TYPES = [
'auto',
'esm',
'dynamic'
];
// Do an extra walk per module and add interested visitors to the walk.
for (const type of JS_TYPES){
factory.hooks.parser.for('javascript/' + type).tap(this.constructor.name, (parser)=>{
/**
* Webpack fun facts:
* `parser.hooks.call.for` cannot catch calls for user defined identifiers like `__jsx`
* it can only detect calls for native objects like `window`, `this`, `eval` etc.
* In order to be able to catch calls of variables like `__jsx`, first we need to catch them as
* Identifier and then return `BasicEvaluatedExpression` whose `id` and `type` webpack matches to
* invoke hook for call.
* See: https://github.com/webpack/webpack/blob/webpack-4/lib/Parser.js#L1931-L1932.
*/ parser.hooks.evaluate.for('Identifier').tap(this.constructor.name, (node)=>{
var ref, ref1;
// We will only optimize fonts from first party code.
if (parser === null || parser === void 0 ? void 0 : (ref = parser.state) === null || ref === void 0 ? void 0 : (ref1 = ref.module) === null || ref1 === void 0 ? void 0 : ref1.resource.includes('node_modules')) {
return;
}
let result;
if (node.name === '_jsx' || node.name === '__jsx') {
result = new _webpack.BasicEvaluatedExpression();
// @ts-ignore
result.setRange(node.range);
result.setExpression(node);
result.setIdentifier(node.name);
// This was added in webpack 5.
result.getMembers = ()=>[]
;
}
return result;
});
const jsxNodeHandler = (node)=>{
var ref, ref2;
if (node.arguments.length !== 2) {
// A font link tag has only two arguments rel=stylesheet and href='...'
return;
}
if (!isNodeCreatingLinkElement(node)) {
return;
}
// node.arguments[0] is the name of the tag and [1] are the props.
const arg1 = node.arguments[1];
const propsNode = arg1.type === 'ObjectExpression' ? arg1 : undefined;
const props = {};
if (propsNode) {
propsNode.properties.forEach((prop)=>{
if (prop.type !== 'Property') {
return;
}
if (prop.key.type === 'Identifier' && prop.value.type === 'Literal') {
props[prop.key.name] = prop.value.value;
}
});
}
if (!props.rel || props.rel !== 'stylesheet' || !props.href || !_constants.OPTIMIZED_FONT_PROVIDERS.some(({ url })=>props.href.startsWith(url)
)) {
return false;
}
this.gatheredStylesheets.push(props.href);
const buildInfo = parser === null || parser === void 0 ? void 0 : (ref = parser.state) === null || ref === void 0 ? void 0 : (ref2 = ref.module) === null || ref2 === void 0 ? void 0 : ref2.buildInfo;
if (buildInfo) {
buildInfo.valueDependencies.set(_constants.FONT_MANIFEST, this.gatheredStylesheets);
}
};
// React JSX transform:
parser.hooks.call.for('_jsx').tap(this.constructor.name, jsxNodeHandler);
// Next.js JSX transform:
parser.hooks.call.for('__jsx').tap(this.constructor.name, jsxNodeHandler);
// New React JSX transform:
parser.hooks.call.for('imported var').tap(this.constructor.name, jsxNodeHandler);
});
}
};
this.isLikeServerless = isLikeServerless;
}
apply(compiler) {
this.compiler = compiler;
compiler.hooks.normalModuleFactory.tap(this.constructor.name, this.parserHandler);
compiler.hooks.make.tapAsync(this.constructor.name, (compilation, cb)=>{
if (this.isLikeServerless) {
/**
* Inline font manifest for serverless case only.
* For target: server drive the manifest through physical file and less of webpack magic.
*/ const mainTemplate = compilation.mainTemplate;
mainTemplate.hooks.requireExtensions.tap(this.constructor.name, (source)=>{
return `${source}
// Font manifest declaration
__webpack_require__.__NEXT_FONT_MANIFEST__ = ${JSON.stringify(this.manifestContent)};
// Enable feature:
process.env.__NEXT_OPTIMIZE_FONTS = JSON.stringify(true);`;
});
}
compilation.hooks.finishModules.tapAsync(this.constructor.name, async (modules, modulesFinished)=>{
let fontStylesheets = this.gatheredStylesheets;
const fontUrls = new Set();
modules.forEach((module)=>{
var ref, ref3;
const fontDependencies = module === null || module === void 0 ? void 0 : (ref = module.buildInfo) === null || ref === void 0 ? void 0 : (ref3 = ref.valueDependencies) === null || ref3 === void 0 ? void 0 : ref3.get(_constants.FONT_MANIFEST);
if (fontDependencies) {
fontDependencies.forEach((v)=>fontUrls.add(v)
);
}
});
fontStylesheets = Array.from(fontUrls);
const fontDefinitionPromises = fontStylesheets.map((url)=>(0, _fontUtils).getFontDefinitionFromNetwork(url)
);
this.manifestContent = [];
for(let promiseIndex in fontDefinitionPromises){
const css = await fontDefinitionPromises[promiseIndex];
if (css) {
try {
const content = await minifyCss(css);
this.manifestContent.push({
url: fontStylesheets[promiseIndex],
content
});
} catch (err) {
Log.warn(`Failed to minify the stylesheet for ${fontStylesheets[promiseIndex]}. Skipped optimizing this font.`);
console.error(err);
}
}
}
compilation.assets[_constants.FONT_MANIFEST] = new _webpack.sources.RawSource(JSON.stringify(this.manifestContent, null, ' '));
modulesFinished();
});
cb();
});
compiler.hooks.make.tap(this.constructor.name, (compilation)=>{
// @ts-ignore TODO: Remove ignore when webpack 5 is stable
compilation.hooks.processAssets.tap({
name: this.constructor.name,
// @ts-ignore TODO: Remove ignore when webpack 5 is stable
stage: _webpack.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS
}, (assets)=>{
assets['../' + _constants.FONT_MANIFEST] = new _webpack.sources.RawSource(JSON.stringify(this.manifestContent, null, ' '));
});
});
}
}
exports.FontStylesheetGatheringPlugin = FontStylesheetGatheringPlugin;
function isNodeCreatingLinkElement(node) {
const callee = node.callee;
if (callee.type !== 'Identifier') {
return false;
}
const componentNode = node.arguments[0];
if (componentNode.type !== 'Literal') {
return false;
}
// React has pragma: _jsx.
// Next has pragma: __jsx.
return (callee.name === '_jsx' || callee.name === '__jsx') && componentNode.value === 'link';
}
//# sourceMappingURL=font-stylesheet-gathering-plugin.js.map
;