@mapbox/batfish
Version:
The React-powered static-site generator you didn't know you wanted
126 lines (116 loc) • 4.44 kB
JavaScript
//
;
const _ = require('lodash');
const fs = require('fs');
const path = require('path');
const pify = require('pify');
const slugg = require('slugg');
const prettier = require('prettier');
const micromatch = require('micromatch');
const getPagesData = require('./get-pages-data');
const writePageModule = require('./write-page-module');
const writeDataModules = require('./write-data-modules');
// Create a JS module that can be used during Webpack compilation,
// exposing important things. See the type definition for BatfishContext.
//
// The file is written to the temporaryDirectory and made available with a
// Webpack alias. **Intended for internal use only.**
//
// Looking at a generated batfish-context.js file will help a lot if you're
// trying to understand how this works.
//
// Returned Promise resolves with the absolute path to the written module file.
function writeContextModule(
batfishConfig
) {
const filePath = path.join(
batfishConfig.temporaryDirectory,
'batfish-context.js'
);
return getPagesData(batfishConfig).then(pagesData => {
// Construct a data object for the entire site.
// Pages can load this data with siteData front-matter
const siteData = {
pages: _.values(pagesData)
};
// This will be overridden if the user has defined a 404 file.
let notFoundStringifiedRouteData = `{
path: '',
getPage: () => { throw new Error('No matching route.'); },
is404: true
}`;
// Our aim is to write a file, not create a JS data structure.
// So we stringify each item as we build it. We'll accumulate
// this array asynchronously (order doesn't matter), then
// join the items to create the fully stringified list, ready
// to be written.
const stringifiedRoutes = [];
// index is used to keep these routes in the original order
const stringifyPageRoute = (
pagePath ,
index
) => {
const pageData = pagesData[pagePath];
const pageWhitelist = batfishConfig.includePages;
if (pageWhitelist && !micromatch.any(pageData.path, pageWhitelist)) {
return Promise.resolve();
}
return writePageModule(batfishConfig, pageData).then(
pageModuleFilePath => {
const is404 =
pageData.filePath.replace(/\.(js|md)$/, '') ===
path.join(batfishConfig.pagesDirectory, '404');
const chunkName = is404 ? 'not-found' : slugg(pagePath) || 'home';
const stringifiedRouteData = `{
path: '${pagePath}',
getPage: () => import(
/* webpackChunkName: "${chunkName}" */
'${pageModuleFilePath}'
),
${
pageData.frontMatter.internalRouting ? 'internalRouting: true,' : ''
}
${is404 ? 'is404: true,' : ''}
}`;
if (is404) {
notFoundStringifiedRouteData = stringifiedRouteData;
}
// Maintain order to avoid unnecessary cache busting.
stringifiedRoutes[index] = stringifiedRouteData;
}
);
};
return writeDataModules(batfishConfig, siteData)
.then(() => {
// Sort before mapping to get the paths in a deterministic order.
// (There cannot be duplicate paths, so no need to worry about unstable
// sorts.)
return Promise.all(
Object.keys(pagesData)
.sort()
.map(stringifyPageRoute)
);
})
.then(() => {
const stringifiedRoutesArray = `[${_.compact(stringifiedRoutes).join(
','
)}]`;
// Webpack fills it with ansi colors so it's hard
// to read in the devtools console. Strip those colors.
const content = `
export const batfishContext = {
selectedConfig: {
siteBasePath: '${String(batfishConfig.siteBasePath)}',
siteOrigin: '${String(batfishConfig.siteOrigin)}',
hijackLinks: ${String(batfishConfig.hijackLinks)}
},
routes: ${stringifiedRoutesArray},
notFoundRoute: ${notFoundStringifiedRouteData}
};`;
const prettyContent = prettier.format(content);
return pify(fs.writeFile)(filePath, prettyContent);
})
.then(() => filePath);
});
}
module.exports = writeContextModule;