1
0
Fork 0
mirror of https://github.com/terribleplan/next.js.git synced 2024-01-19 02:48:18 +00:00

Parcel build

This commit is contained in:
Tim Neutkens 2018-10-20 14:23:43 +02:00
parent 02acd1b608
commit 3640738f36
9 changed files with 372 additions and 14 deletions

1
.gitignore vendored
View file

@ -19,3 +19,4 @@ coverage
# test output
test/**/out
.DS_Store
.cache

View file

@ -0,0 +1,299 @@
const JSPackager = require('parcel/src/packagers/JSPackager')
const {glob} = require('parcel/src/utils/glob')
const urlJoin = require('parcel/src/utils/urlJoin');
const path = require('path');
const {promisify} = require('util')
const fs = require('fs')
const loadConfig = require('next-server/next-config').default
const { PHASE_PRODUCTION_BUILD, BUILD_ID_FILE } = require('next-server/constants')
const addMDXtoBundler = require('@mdx-js/parcel-plugin-mdx')
const access = promisify(fs.access)
const writeFile = promisify(fs.writeFile)
const PAGES_ROUTE_NAME_REGEX = /^pages[/\\](.*)\.js$/
const DEFAULT_ROUTE_REGEX = /[/\\]next[/\\]dist[/\\]pages[/\\](.*)\.js$/
const ROUTE_NAME_REGEX = /static[/\\][^/\\]+[/\\]pages[/\\](.*)\.js$/
const ROUTE_NAME_SERVER_REGEX = /[/\\]server[/\\]pages[/\\](.*)\.js$/
const NEXT_RUNTIME_REGEX = /[/\\]next[/\\]dist[/\\]client[/\\]next/
process.env.NODE_ENV = 'production'
async function writeBuildId (distDir, buildId) {
const buildIdPath = path.join(distDir, BUILD_ID_FILE)
await writeFile(buildIdPath, buildId, 'utf8')
}
function normalizePageName(page) {
if(page === 'index') {
return page
}
return page.replace(/(^|\/)index$/, '')
}
function createServerPackager() {
return class NextServerPackager extends JSPackager {
getBundleSpecifier(bundle) {
let name = path.relative(path.dirname(this.bundle.name), bundle.name);
if (bundle.entryAsset) {
return [name, bundle.entryAsset.id];
}
return name;
}
async end() {
let entry = [];
// Add the HMR runtime if needed.
if (this.options.hmr) {
let asset = await this.bundler.getAsset(
require.resolve('../builtins/hmr-runtime')
);
await this.addAssetToBundle(asset);
entry.push(asset.id);
}
if (await this.writeBundleLoaders()) {
entry.push(0);
}
if (this.bundle.entryAsset && this.externalModules.size === 0) {
entry.push(this.bundle.entryAsset.id);
}
await this.dest.write(
`},(typeof window !== 'undefined' ? (global.__NEXT_CACHE__ = global.__NEXT_CACHE__ || {}) : {}),` +
JSON.stringify(entry) +
', ' +
JSON.stringify(this.options.global || null) +
')'
);
if (this.options.sourceMaps) {
// Add source map url if a map bundle exists
let mapBundle = this.bundle.siblingBundlesMap.get('map');
if (mapBundle) {
let mapUrl = urlJoin(
this.options.publicURL,
path.basename(mapBundle.name)
);
await this.write(`\n//# sourceMappingURL=${mapUrl}`);
}
}
await this.dest.end();
}
}
}
function createClientPackager({buildId}) {
return class NextClientPackager extends JSPackager {
getBundleSpecifier(bundle) {
let name = path.relative(path.dirname(this.bundle.name), bundle.name);
if (bundle.entryAsset) {
return [name, bundle.entryAsset.id];
}
return name;
}
async start() {
const result = ROUTE_NAME_REGEX.exec(this.bundle.name) || DEFAULT_ROUTE_REGEX.exec(this.bundle.name)
if(result) {
this.isPageBundle = true
let routeName = result ? result[1] : defaultPage[1]
// We need to convert \ into / when we are in windows
// to get the proper route name
// Here we need to do windows check because it's possible
// to have "\" in the filename in unix.
// Anyway if someone did that, he'll be having issues here.
// But that's something we cannot avoid.
if (/^win/.test(process.platform)) {
routeName = routeName.replace(/\\/g, '/')
}
routeName = `/${routeName.replace(/(^|\/)index$/, '')}`
await this.write(`__NEXT_REGISTER_PAGE("${routeName}", function() {var module={};var exports={};`)
}
await super.start()
}
async end() {
let entry = [];
// Add the HMR runtime if needed.
if (this.options.hmr) {
let asset = await this.bundler.getAsset(
require.resolve('../builtins/hmr-runtime')
);
await this.addAssetToBundle(asset);
entry.push(asset.id);
}
if (await this.writeBundleLoaders()) {
entry.push(0);
}
if (this.bundle.entryAsset && this.externalModules.size === 0) {
entry.push(this.bundle.entryAsset.id);
}
await this.dest.write(
`},(typeof window !== 'undefined' ? (window.__NEXT_CACHE__ = window.__NEXT_CACHE__ || {}) : {}),` +
JSON.stringify(entry) +
', ' +
JSON.stringify(this.options.global || null) +
')'
);
if(this.isPageBundle) {
await this.dest.write(
';return {page: module.exports.default}})'
)
}
if (this.options.sourceMaps) {
// Add source map url if a map bundle exists
let mapBundle = this.bundle.siblingBundlesMap.get('map');
if (mapBundle) {
let mapUrl = urlJoin(
this.options.publicURL,
path.basename(mapBundle.name)
);
await this.write(`\n//# sourceMappingURL=${mapUrl}`);
}
}
await this.dest.end();
}
}
}
function getPageFiles({dir, config, isClient}) {
const result = glob.sync(`pages/**/${isClient ? '!(_document)' : ''}*.+(${config.pageExtensions.join('|')})`, { cwd: dir, absolute: true })
const appPath = path.join(dir, 'pages', '_app.js')
if(!result.some((item) => item === appPath)) {
result.push(require.resolve('next/dist/pages/_app.js'))
}
const errorPath = path.join(dir, 'pages', '_error.js')
if(!result.some((item) => item === errorPath)) {
result.push(require.resolve('next/dist/pages/_error.js'))
}
if(!isClient) {
const documentPath = path.join(dir, 'pages', '_document.js')
if(!result.some((item) => item === documentPath)) {
result.push(require.resolve('next/dist/pages/_document.js'))
}
}
return result
}
async function clientBundler({Bundler, dir, buildId, config}) {
const clientPages = getPageFiles({dir, config, isClient: true})
const entryFiles = [
require.resolve('next/dist/client/next.js'),
...clientPages
];
const options = {
outDir: path.join(dir, '.next', 'static'),
watch: false,
sourceMaps: false,
minify: true,
target: 'browser'
}
// Initializes a bundler using the entrypoint location and options provided
const bundler = new Bundler(entryFiles, options);
// Make sure pages get resolved from the root dir
bundler.options.rootDir = dir
bundler.addPackager('js', createClientPackager({buildId}))
bundler.addAssetType('js', path.join(__dirname, 'nextjsasset.js'))
bundler.addAssetType('jsx', path.join(__dirname, 'nextjsasset.js'))
addMDXtoBundler(bundler)
// Run the bundler, this returns the main bundle
// Use the events if you're using watch mode as this promise will only trigger once and not for every rebuild
const bundle = await bundler.bundle();
}
async function serverBundler({Bundler, dir, buildId, config}) {
const serverPages = getPageFiles({dir, config})
const entryFiles = serverPages
const options = {
outDir: path.join(dir, '.next', 'server'),
watch: false,
sourceMaps: false,
minify: true,
target: 'node'
}
// Initializes a bundler using the entrypoint location and options provided
const bundler = new Bundler(entryFiles, options);
// Make sure pages get resolved from the root dir
bundler.options.rootDir = dir
bundler.addPackager('js', createServerPackager())
bundler.addAssetType('js', path.join(__dirname, 'nextjsasset.js'))
bundler.addAssetType('jsx', path.join(__dirname, 'nextjsasset.js'))
addMDXtoBundler(bundler)
// Run the bundler, this returns the main bundle
// Use the events if you're using watch mode as this promise will only trigger once and not for every rebuild
const bundle = await bundler.bundle();
}
function rewriteFileName(Bundle, buildId) {
const nextClientRuntime = require.resolve('next/dist/client/next.js')
const originalGetHashedBundleName = Bundle.prototype.getHashedBundleName
Bundle.prototype.getHashedBundleName = function getHashedBundleName(contentHash) {
const entryAsset = this.entryAsset || this.parentBundle.entryAsset;
const isEntry = entryAsset.options.entryFiles.includes(entryAsset.name) || Array.from(entryAsset.parentDeps).some(dep => dep.entry);
const originalResult = originalGetHashedBundleName.call(this, contentHash)
if(!isEntry) {
return originalResult
}
if(entryAsset.name === nextClientRuntime) {
return path.join(buildId, 'main.js')
}
const result = PAGES_ROUTE_NAME_REGEX.exec(originalResult) || DEFAULT_ROUTE_REGEX.exec(originalResult)
if(!result) {
return originalResult
}
const page = normalizePageName(result[1])
const ext = path.extname(originalResult)
const normalizedPagePath = path.join(buildId, 'pages', page + ext)
return normalizedPagePath
}
}
module.exports = async function build({dir, conf}) {
const config = loadConfig(PHASE_PRODUCTION_BUILD, dir, conf)
const buildId = await config.generateBuildId().trim() // defaults to a uuid
const distDir = path.join(dir, config.distDir)
try {
await access(dir, (fs.constants || fs).W_OK)
} catch (err) {
console.error(`> Failed, build directory is not writeable. https://err.sh/zeit/next.js/build-dir-not-writeable`)
throw err
}
const Bundle = require('parcel/src/Bundle')
rewriteFileName(Bundle, buildId)
const Bundler = require('parcel');
await serverBundler({Bundler, dir, buildId, config})
await clientBundler({Bundler, dir, buildId, config})
await writeBuildId(distDir, buildId)
}

View file

@ -0,0 +1,44 @@
const JSAsset = require('parcel/src/assets/JSAsset');
const envVisitor = require('parcel/src/visitors/env');
const ENV_RE = /\b(?:process\.env)\b/;
const babel7 = require('parcel/src/transforms/babel/babel7');
const getBabelConfig = require('parcel/src/transforms/babel/config');
async function babelTransform(asset) {
// let config = await getBabelConfig(asset);
// console.log(config[7])
// if (config[6]) {
// await babel6(asset, config[6]);
// }
// if (config[7]) {
await babel7(asset, {
babelVersion: 7,
config: {
presets: ['next/babel']
}
});
// }
return asset.ast;
}
class NextJSAsset extends JSAsset {
async pretransform() {
if (this.options.sourceMaps) {
await this.loadSourceMap();
}
await babelTransform(this);
// Inline environment variables
if (this.options.target === 'browser' && ENV_RE.test(this.contents)) {
await this.parseIfNeeded();
this.traverseFast(envVisitor);
}
}
}
module.exports = NextJSAsset

View file

@ -0,0 +1,11 @@
{
"name": "next-build",
"version": "7.0.2-canary.8",
"main": "index.js",
"dependencies": {
"@mdx-js/parcel-plugin-mdx": "0.15.5",
"@mdx-js/tag": "0.15.0",
"next-server": "^7.0.2-canary.8",
"parcel": "1.10.3"
}
}

View file

@ -76,7 +76,7 @@ async function doRender (req, res, pathname, query, {
await hotReloader.ensurePage(page)
}
const pagePath = join(distDir, SERVER_DIRECTORY, CLIENT_STATIC_FILES_PATH, buildId, 'pages', normalizedPagePath)
const pagePath = join(distDir, SERVER_DIRECTORY, /* CLIENT_STATIC_FILES_PATH, */ buildId, 'pages', normalizedPagePath)
try {
await access(`${pagePath}.js`, (fs.constants || fs).R_OK)
@ -84,8 +84,8 @@ async function doRender (req, res, pathname, query, {
throw pageNotFoundError(page)
}
const documentPath = join(distDir, SERVER_DIRECTORY, CLIENT_STATIC_FILES_PATH, buildId, 'pages', '_document')
const appPath = join(distDir, SERVER_DIRECTORY, CLIENT_STATIC_FILES_PATH, buildId, 'pages', '_app')
const documentPath = join(distDir, SERVER_DIRECTORY, /* CLIENT_STATIC_FILES_PATH, */ buildId, 'pages', '_document')
const appPath = join(distDir, SERVER_DIRECTORY, /* CLIENT_STATIC_FILES_PATH, */ buildId, 'pages', '_app')
let Document = require(documentPath)
let App = require(appPath)
@ -95,8 +95,8 @@ async function doRender (req, res, pathname, query, {
App = App.default || App
Component = Component.default || Component
const reactLoadableManifest = require(pagePath + '-loadable.json')
const buildManifest = require(pagePath + '-assets.json')
// const reactLoadableManifest = require(pagePath + '-loadable.json')
// const buildManifest = require(pagePath + '-assets.json')
if (typeof Component !== 'function') {
throw new Error(`The default export is not a React Component in page: "${page}"`)
@ -106,7 +106,8 @@ async function doRender (req, res, pathname, query, {
const ctx = { err, req, res, pathname, query, asPath }
const router = new Router(pathname, query, asPath)
const props = await loadGetInitialProps(App, {Component, router, ctx})
const files = buildManifest.assets
// const files = buildManifest.assets
const files = [`static/${buildId}/main.js`]
// the response might be finshed on the getinitialprops call
if (isResSent(res)) return
@ -153,13 +154,14 @@ async function doRender (req, res, pathname, query, {
head = Head.rewind() || defaultHead()
}
return { html, head, buildManifest }
return { html, head, /* buildManifest */ }
}
await Loadable.preloadAll() // Make sure all dynamic imports are loaded
const docProps = await loadGetInitialProps(Document, { ...ctx, renderPage })
const dynamicImports = [...(new Set(getDynamicImportBundles(reactLoadableManifest, reactLoadableModules)))]
// const dynamicImports = [...(new Set(getDynamicImportBundles(reactLoadableManifest, reactLoadableModules)))]
const dynamicImports = []
const dynamicImportsIds = dynamicImports.map((bundle) => bundle.id)
if (isResSent(res)) return
@ -181,7 +183,7 @@ async function doRender (req, res, pathname, query, {
dev,
dir,
staticMarkup,
buildManifest,
// buildManifest,
files,
dynamicImports,
assetPrefix, // We always pass assetPrefix as a top level property since _document needs it to render, even though the client side might not need it

View file

@ -2,7 +2,8 @@
import { resolve, join } from 'path'
import { existsSync } from 'fs'
import parseArgs from 'minimist'
import build from '../build'
// import build from '../build'
import build from 'next-build'
import { printAndExit } from '../server/lib/utils'
const argv = parseArgs(process.argv.slice(2), {

View file

@ -46,7 +46,7 @@ module.exports = (context, opts = {}) => ({
plugins: [
require('babel-plugin-react-require'),
require('@babel/plugin-syntax-dynamic-import'),
require('./plugins/react-loadable-plugin'),
// require('./plugins/react-loadable-plugin'),
require('./plugins/next-to-next-server'),
[require('@babel/plugin-proposal-class-properties'), opts['class-properties'] || {}],
require('@babel/plugin-proposal-object-rest-spread'),

View file

@ -37,7 +37,7 @@ const prefix = assetPrefix || ''
// With dynamic assetPrefix it's no longer possible to set assetPrefix at the build time
// So, this is how we do it in the client side at runtime
__webpack_public_path__ = `${prefix}/_next/` //eslint-disable-line
// __webpack_public_path__ = `${prefix}/_next/` //eslint-disable-line
// Initialize next/asset with the assetPrefix
asset.setAssetPrefix(prefix)
// Initialize next/config with the environment configuration
@ -48,7 +48,7 @@ envConfig.setConfig({
const asPath = getURL()
const pageLoader = new PageLoader(buildId, prefix)
const pageLoader = new PageLoader({buildId, assetPrefix: prefix})
window.__NEXT_LOADED_PAGES__.forEach(([r, f]) => {
pageLoader.registerPage(r, f)
})

View file

@ -4,7 +4,7 @@ import EventEmitter from 'next-server/dist/lib/EventEmitter'
const webpackModule = module
export default class PageLoader {
constructor (buildId, assetPrefix) {
constructor ({buildId, assetPrefix}) {
this.buildId = buildId
this.assetPrefix = assetPrefix