diff --git a/package.json b/package.json index eaf257b5..1ff65dfd 100644 --- a/package.json +++ b/package.json @@ -52,8 +52,8 @@ "fkill": "5.1.0", "flatten": "1.0.2", "get-port": "3.2.0", - "jest-junit": "^5.0.0", "jest-cli": "23.6.0", + "jest-junit": "^5.0.0", "lerna": "^3.4.0", "lint-staged": "4.2.3", "mkdirp": "0.5.1", @@ -67,7 +67,8 @@ "standard": "11.0.1", "taskr": "1.1.0", "wait-port": "0.2.2", - "wd": "1.10.3" + "wd": "1.10.3", + "webpack-bundle-analyzer": "3.0.3" }, "engines": { "node": ">= 8.0.0" diff --git a/packages/next-server/babel.config.js b/packages/next-server/babel.config.js deleted file mode 100644 index 56fe8305..00000000 --- a/packages/next-server/babel.config.js +++ /dev/null @@ -1,16 +0,0 @@ -module.exports = { - 'presets': [ - '@babel/preset-env', - '@babel/preset-react' - ], - 'plugins': [ - '@babel/plugin-proposal-object-rest-spread', - '@babel/plugin-proposal-class-properties', - ['@babel/plugin-transform-runtime', { - 'corejs': 2 - }], - ['babel-plugin-transform-define', { - 'process.env.NEXT_VERSION': require('./package.json').version - }] - ] -} diff --git a/packages/next-server/lib/EventEmitter.js b/packages/next-server/lib/event-emitter.js similarity index 100% rename from packages/next-server/lib/EventEmitter.js rename to packages/next-server/lib/event-emitter.js diff --git a/packages/next-server/lib/router/router.js b/packages/next-server/lib/router/router.js index 9ae1e281..ee48b2f5 100644 --- a/packages/next-server/lib/router/router.js +++ b/packages/next-server/lib/router/router.js @@ -1,10 +1,9 @@ /* global __NEXT_DATA__ */ import { parse, format } from 'url' -import EventEmitter from '../EventEmitter' -import shallowEquals from '../shallow-equals' +import EventEmitter from '../event-emitter' +import shallowEquals from './shallow-equals' import { loadGetInitialProps, getURL } from '../utils' -import { _rewriteUrlForNextExport } from './' export default class Router { static events = new EventEmitter() @@ -46,6 +45,30 @@ export default class Router { } } + static _rewriteUrlForNextExport (url) { + const [, hash] = url.split('#') + url = url.replace(/#.*/, '') + + let [path, qs] = url.split('?') + path = path.replace(/\/$/, '') + + let newPath = path + // Append a trailing slash if this path does not have an extension + if (!/\.[^/]+\/?$/.test(path)) { + newPath = `${path}/` + } + + if (qs) { + newPath = `${newPath}?${qs}` + } + + if (hash) { + newPath = `${newPath}#${hash}` + } + + return newPath + } + onPopState = e => { if (!e.state) { // We get state as undefined for two reasons. @@ -147,7 +170,7 @@ export default class Router { // Add the ending slash to the paths. So, we can serve the // "/index.html" directly for the SSR page. if (__NEXT_DATA__.nextExport) { - as = _rewriteUrlForNextExport(as) + as = Router._rewriteUrlForNextExport(as) } this.abortComponentLoad(as) diff --git a/packages/next-server/lib/shallow-equals.js b/packages/next-server/lib/router/shallow-equals.js similarity index 100% rename from packages/next-server/lib/shallow-equals.js rename to packages/next-server/lib/router/shallow-equals.js diff --git a/packages/next-server/lib/utils.js b/packages/next-server/lib/utils.js index b6144951..77c4be82 100644 --- a/packages/next-server/lib/utils.js +++ b/packages/next-server/lib/utils.js @@ -34,8 +34,7 @@ export function isResSent (res) { export async function loadGetInitialProps (Component, ctx) { if (process.env.NODE_ENV !== 'production') { if (Component.prototype && Component.prototype.getInitialProps) { - const compName = getDisplayName(Component) - const message = `"${compName}.getInitialProps()" is defined as an instance method - visit https://err.sh/zeit/next.js/get-initial-props-as-an-instance-method for more information.` + const message = `"${getDisplayName(Component)}.getInitialProps()" is defined as an instance method - visit https://err.sh/zeit/next.js/get-initial-props-as-an-instance-method for more information.` throw new Error(message) } } @@ -49,8 +48,7 @@ export async function loadGetInitialProps (Component, ctx) { } if (!props) { - const compName = getDisplayName(Component) - const message = `"${compName}.getInitialProps()" should resolve to an object. But found "${props}" instead.` + const message = `"${getDisplayName(Component)}.getInitialProps()" should resolve to an object. But found "${props}" instead.` throw new Error(message) } diff --git a/packages/next-server/link.js b/packages/next-server/link.js deleted file mode 100644 index c5afd84d..00000000 --- a/packages/next-server/link.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./dist/lib/link') diff --git a/packages/next-server/package.json b/packages/next-server/package.json index 9eff1bff..58f55631 100644 --- a/packages/next-server/package.json +++ b/packages/next-server/package.json @@ -22,17 +22,13 @@ }, "taskr": { "requires": [ - "./taskfile-babel.js" + "./taskfile-typescript.js" ] }, "dependencies": { - "@babel/runtime-corejs2": "7.1.2", - "ansi-html": "0.0.7", "etag": "1.8.1", "find-up": "3.0.0", "fresh": "0.5.2", - "hoist-non-react-statics": "3.0.1", - "htmlescape": "1.1.1", "path-to-regexp": "2.1.0", "prop-types": "15.6.2", "send": "0.16.1", @@ -43,16 +39,10 @@ "react-dom": "^16.0.0" }, "devDependencies": { - "@babel/core": "7.1.2", - "@babel/plugin-proposal-class-properties": "7.1.0", - "@babel/plugin-proposal-object-rest-spread": "7.0.0", - "@babel/plugin-syntax-dynamic-import": "7.0.0", - "@babel/plugin-transform-runtime": "7.1.0", - "@babel/preset-env": "7.1.0", - "@babel/preset-react": "7.0.0", "@taskr/clear": "1.1.0", "@taskr/watch": "1.1.0", - "taskr": "1.1.0" + "taskr": "1.1.0", + "typescript": "3.1.6" }, "engines": { "node": ">= 8.0.0" diff --git a/packages/next-server/router.js b/packages/next-server/router.js deleted file mode 100644 index b8c9ff94..00000000 --- a/packages/next-server/router.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./dist/lib/router') diff --git a/packages/next-server/server/render.js b/packages/next-server/server/render.js index b6ed6fb3..52adcb5a 100644 --- a/packages/next-server/server/render.js +++ b/packages/next-server/server/render.js @@ -5,10 +5,9 @@ import send from 'send' import generateETag from 'etag' import fresh from 'fresh' import requirePage, {normalizePagePath} from './require' -import { Router } from '../lib/router' +import Router from '../lib/router/router' import { loadGetInitialProps, isResSent } from '../lib/utils' import Head, { defaultHead } from '../lib/head' -import ErrorDebug from '../lib/error-debug' import Loadable from '../lib/loadable' import LoadableCapture from '../lib/loadable-capture' import { BUILD_MANIFEST, REACT_LOADABLE_MANIFEST, SERVER_DIRECTORY, CLIENT_STATIC_FILES_PATH } from 'next-server/constants' @@ -137,6 +136,7 @@ async function doRender (req, res, pathname, query, { try { if (err && dev) { + const ErrorDebug = require(join(distDir, SERVER_DIRECTORY, 'error-debug')).default html = render() } else { html = render(app) diff --git a/packages/next-server/taskfile-babel.js b/packages/next-server/taskfile-babel.js deleted file mode 100644 index c44e9a1c..00000000 --- a/packages/next-server/taskfile-babel.js +++ /dev/null @@ -1,65 +0,0 @@ -// taskr babel plugin with Babel 7 support -// https://github.com/lukeed/taskr/pull/305 -'use strict' - -const transform = require('@babel/core').transform -const flatten = require('flatten') - -const BABEL_REGEX = /(^@babel\/)(preset|plugin)-(.*)/i - -function getBabels () { - const pkg = require('./package.json') - return flatten( - ['devDependencies', 'dependencies'].map(s => Object.keys(pkg[s] || {})) - ).filter(s => BABEL_REGEX.test(s)) -} - -module.exports = function (task) { - let cache - - task.plugin('babel', {}, function * (file, opts) { - if (opts.preload) { - delete opts.preload - // get dependencies - cache = cache || getBabels() - - // attach any deps to babel's `opts` - cache.forEach(dep => { - const segs = BABEL_REGEX.exec(dep) - const type = `${segs[2]}s` - const name = `@babel/${segs[2]}-${segs[3]}` - - opts[type] = opts[type] || [] - - // flatten all (advanced entries are arrays) - if (flatten(opts[type]).indexOf(name) === -1) { - opts[type] = opts[type].concat(name) - } - }) - } - - // attach file's name - opts.filename = file.base - - const output = transform(file.data, opts) - - if (output.map) { - const map = `${file.base}.map` - - // append `sourceMappingURL` to original file - if (opts.sourceMaps !== 'both') { - output.code += Buffer.from(`\n//# sourceMappingURL=${map}`) - } - - // add sourcemap to `files` array - this._.files.push({ - base: map, - dir: file.dir, - data: Buffer.from(JSON.stringify(output.map)) - }) - } - - // update file's data - file.data = Buffer.from(output.code) - }) -} diff --git a/packages/next-server/taskfile-typescript.js b/packages/next-server/taskfile-typescript.js new file mode 100644 index 00000000..f200e935 --- /dev/null +++ b/packages/next-server/taskfile-typescript.js @@ -0,0 +1,43 @@ +'use strict' +try { + const ts = require('typescript') + const extname = require('path').extname + const config = require('./tsconfig.json') + + module.exports = function (task) { + task.plugin('typescript', { every: true }, function * (file, options) { + const opts = { + fileName: file.base, + compilerOptions: { + ...config.compilerOptions, + ...options + } + } + + const ext = extname(file.base) + // For example files without an extension don't have to be rewritten + if (ext) { + // Replace `.ts` with `.js` + const extRegex = new RegExp(ext.replace('.', '\\.') + '$', 'i') + file.base = file.base.replace(extRegex, '.js') + } + + // compile output + const result = ts.transpileModule(file.data.toString(), opts) + + if (opts.compilerOptions.sourceMap && result.sourceMapText) { + // add sourcemap to `files` array + this._.files.push({ + dir: file.dir, + base: `${file.base}.map`, + data: Buffer.from(JSON.stringify(result.sourceMapText), 'utf8') + }) + } + + // update file's data + file.data = Buffer.from(result.outputText.replace(/process\.env\.NEXT_VERSION/, `"${require('./package.json').version}"`), 'utf8') + }) + } +} catch (err) { + console.error(err) +} diff --git a/packages/next-server/taskfile.js b/packages/next-server/taskfile.js index c317838c..97ce6a5e 100644 --- a/packages/next-server/taskfile.js +++ b/packages/next-server/taskfile.js @@ -1,27 +1,21 @@ const notifier = require('node-notifier') -export async function bin (task, opts) { - await task.source(opts.src || 'bin/*').babel().target('dist/bin', {mode: '0755'}) - notify('Compiled binaries') -} - export async function lib (task, opts) { - await task.source(opts.src || 'lib/**/*.js').babel().target('dist/lib') + await task.source(opts.src || 'lib/**/*.js').typescript({module: 'commonjs'}).target('dist/lib') notify('Compiled lib files') } export async function server (task, opts) { - await task.source(opts.src || 'server/**/*.js').babel().target('dist/server') + await task.source(opts.src || 'server/**/*.js').typescript({module: 'commonjs'}).target('dist/server') notify('Compiled server files') } export async function build (task) { - await task.parallel(['bin', 'server', 'lib']) + await task.parallel(['server', 'lib']) } export default async function (task) { await task.start('build') - await task.watch('bin/*', 'bin') await task.watch('server/**/*.js', 'server') await task.watch('lib/**/*.js', 'lib') } diff --git a/packages/next-server/tsconfig.json b/packages/next-server/tsconfig.json new file mode 100644 index 00000000..1e365ba7 --- /dev/null +++ b/packages/next-server/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "allowJs": true, + "noEmit": true, + "module": "esnext", + "target": "ES2017", + "esModuleInterop": true, + "jsx": "react" + } +} diff --git a/packages/next/.flowconfig b/packages/next/.flowconfig deleted file mode 100644 index 5183c2e1..00000000 --- a/packages/next/.flowconfig +++ /dev/null @@ -1,2 +0,0 @@ -[ignore] -/.*.json \ No newline at end of file diff --git a/packages/next/babel.config.js b/packages/next/babel.config.js deleted file mode 100644 index 4f0ab221..00000000 --- a/packages/next/babel.config.js +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = { - 'presets': [ - '@babel/preset-env', - '@babel/preset-react' - ], - 'plugins': [ - '@babel/plugin-syntax-dynamic-import', - '@babel/plugin-proposal-object-rest-spread', - '@babel/plugin-proposal-class-properties', - ['@babel/plugin-transform-runtime', { - 'corejs': 2 - }], - ['babel-plugin-transform-define', { - 'process.env.NEXT_VERSION': require('./package.json').version - }] - ] -} diff --git a/packages/next/build/babel/plugins/next-to-next-server.js b/packages/next/build/babel/plugins/next-to-next-server.js index 22adf86a..bd2fdd55 100644 --- a/packages/next/build/babel/plugins/next-to-next-server.js +++ b/packages/next/build/babel/plugins/next-to-next-server.js @@ -19,12 +19,6 @@ export default function ({ types: t, template }) { if (source === 'next/head') { path.node.source.value = 'next-server/head' } - if (source === 'next/link') { - path.node.source.value = 'next-server/link' - } - if (source === 'next/router') { - path.node.source.value = 'next-server/router' - } } } } diff --git a/packages/next/build/webpack.js b/packages/next/build/webpack.js index 9eddeb95..d6fffd11 100644 --- a/packages/next/build/webpack.js +++ b/packages/next/build/webpack.js @@ -15,7 +15,7 @@ import BuildManifestPlugin from './webpack/plugins/build-manifest-plugin' import ChunkNamesPlugin from './webpack/plugins/chunk-names-plugin' import { ReactLoadablePlugin } from './webpack/plugins/react-loadable-plugin' import {SERVER_DIRECTORY, REACT_LOADABLE_MANIFEST, CLIENT_STATIC_FILES_RUNTIME_WEBPACK, CLIENT_STATIC_FILES_RUNTIME_MAIN} from 'next-server/constants' -import {NEXT_PROJECT_ROOT, NEXT_PROJECT_ROOT_NODE_MODULES, NEXT_PROJECT_ROOT_DIST, DEFAULT_PAGES_DIR} from '../lib/constants' +import {NEXT_PROJECT_ROOT, NEXT_PROJECT_ROOT_NODE_MODULES, NEXT_PROJECT_ROOT_DIST_CLIENT, NEXT_PROJECT_ROOT_DIST_SERVER, DEFAULT_PAGES_DIR} from '../lib/constants' import AutoDllPlugin from 'autodll-webpack-plugin' import TerserPlugin from 'terser-webpack-plugin' import AssetsSizePlugin from './webpack/plugins/assets-size-plugin' @@ -58,7 +58,7 @@ function externalsConfig (dir, isServer, lambdas) { ] } - const notExternalModules = ['next/app', 'next/document', 'next/error', 'http-status', 'string-hash'] + const notExternalModules = ['next/app', 'next/document', 'next/link', 'next/router', 'next/error', 'http-status', 'string-hash', 'ansi-html', 'hoist-non-react-statics', 'htmlescape'] externals.push((context, request, callback) => { if (notExternalModules.indexOf(request) !== -1) { @@ -71,11 +71,7 @@ function externalsConfig (dir, isServer, lambdas) { } // Default pages have to be transpiled - if (res.match(/next[/\\]dist[/\\]pages/)) { - return callback() - } - - if (res.match(/node_modules[/\\]@babel[/\\]runtime[/\\]/)) { + if (res.match(/next[/\\]dist[/\\]pages/) || res.match(/next[/\\]dist[/\\]client/) || res.match(/node_modules[/\\]@babel[/\\]runtime[/\\]/) || res.match(/node_modules[/\\]@babel[/\\]runtime-corejs2[/\\]/)) { return callback() } @@ -200,9 +196,12 @@ export default async function getBaseWebpackConfig (dir, {dev = false, isServer // Backwards compatibility 'main.js': [], [CLIENT_STATIC_FILES_RUNTIME_MAIN]: [ - path.join(NEXT_PROJECT_ROOT_DIST, 'client', (dev ? `next-dev` : 'next')) + path.join(NEXT_PROJECT_ROOT_DIST_CLIENT, (dev ? `next-dev` : 'next')) ].filter(Boolean) } : {} + const devServerEntries = dev && isServer ? { + 'error-debug.js': path.join(NEXT_PROJECT_ROOT_DIST_SERVER, 'error-debug.js') + } : {} const resolveConfig = { // Disable .mjs for node_modules bundling @@ -233,6 +232,7 @@ export default async function getBaseWebpackConfig (dir, {dev = false, isServer entry: async () => { return { ...clientEntries, + ...devServerEntries, // Only _error and _document when in development. The rest is handled by on-demand-entries ...pagesEntries } @@ -273,8 +273,14 @@ export default async function getBaseWebpackConfig (dir, {dev = false, isServer }, { test: /\.(js|jsx)$/, - include: [dir], - exclude: /node_modules/, + include: [dir, NEXT_PROJECT_ROOT_DIST_CLIENT, DEFAULT_PAGES_DIR], + exclude: (path) => { + if (path.indexOf(NEXT_PROJECT_ROOT_DIST_CLIENT) === 0 || path.indexOf(DEFAULT_PAGES_DIR) === 0) { + return false + } + + return /node_modules/.exec(path) + }, use: defaultLoaders.babel } ].filter(Boolean) diff --git a/packages/next/build/webpack/plugins/nextjs-ssr-module-cache.js b/packages/next/build/webpack/plugins/nextjs-ssr-module-cache.js index 85981cb0..e032bedf 100644 --- a/packages/next/build/webpack/plugins/nextjs-ssr-module-cache.js +++ b/packages/next/build/webpack/plugins/nextjs-ssr-module-cache.js @@ -37,11 +37,15 @@ export default class NextJsSsrImportPlugin { // If the chunk is not part of the pages directory we have to keep the original behavior, // otherwise webpack will error out when the file is used before the compilation finishes // this is the case with mini-css-extract-plugin - if (!IS_BUNDLED_PAGE_REGEX.exec(chunk.name)) { + if (!IS_BUNDLED_PAGE_REGEX.exec(chunk.name) && chunk.name !== 'error-debug.js') { return originalFn(source, chunk) } const pagePath = join(outputPath, dirname(chunk.name)) - const relativePathToBaseDir = relative(pagePath, join(outputPath, SSR_MODULE_CACHE_FILENAME)) + let relativePathToBaseDir = relative(pagePath, join(outputPath, SSR_MODULE_CACHE_FILENAME)) + if (chunk.name === 'error-debug.js') { + relativePathToBaseDir = `./${relativePathToBaseDir}` + } + // Make sure even in windows, the path looks like in unix // Node.js require system will convert it accordingly const relativePathToBaseDirNormalized = relativePathToBaseDir.replace(/\\/g, '/') diff --git a/packages/next/client/index.js b/packages/next/client/index.js index 4a81f8a1..892bb935 100644 --- a/packages/next/client/index.js +++ b/packages/next/client/index.js @@ -1,10 +1,10 @@ import React from 'react' import ReactDOM from 'react-dom' import HeadManager from './head-manager' -import { createRouter } from 'next-server/dist/lib/router' -import EventEmitter from 'next-server/dist/lib/EventEmitter' +import { createRouter } from 'next/router' +import EventEmitter from 'next-server/dist/lib/event-emitter' import {loadGetInitialProps, getURL} from 'next-server/dist/lib/utils' -import PageLoader from '../lib/page-loader' +import PageLoader from './page-loader' import * as asset from 'next-server/asset' import * as envConfig from 'next-server/config' import ErrorBoundary from './error-boundary' diff --git a/packages/next-server/lib/link.js b/packages/next/client/link.js similarity index 96% rename from packages/next-server/lib/link.js rename to packages/next/client/link.js index 971c7806..e1d579e6 100644 --- a/packages/next-server/lib/link.js +++ b/packages/next/client/link.js @@ -3,8 +3,8 @@ import { resolve, format, parse } from 'url' import React, { Component, Children } from 'react' import PropTypes from 'prop-types' -import Router, { _rewriteUrlForNextExport } from './router' -import {execOnce, getLocationOrigin} from './utils' +import Router, {Router as _Router} from 'next/router' +import {execOnce, getLocationOrigin} from 'next-server/dist/lib/utils' function isLocal (href) { const url = parse(href, false, true) @@ -145,7 +145,7 @@ class Link extends Component { typeof __NEXT_DATA__ !== 'undefined' && __NEXT_DATA__.nextExport ) { - props.href = _rewriteUrlForNextExport(props.href) + props.href = _Router._rewriteUrlForNextExport(props.href) } return React.cloneElement(child, props) diff --git a/packages/next/client/on-demand-entries-client.js b/packages/next/client/on-demand-entries-client.js index cc9fe9a4..0f3bd4d4 100644 --- a/packages/next/client/on-demand-entries-client.js +++ b/packages/next/client/on-demand-entries-client.js @@ -1,6 +1,6 @@ /* global location */ -import Router from 'next-server/router' +import Router from 'next/router' import fetch from 'unfetch' export default ({assetPrefix}) => { diff --git a/packages/next/lib/page-loader.js b/packages/next/client/page-loader.js similarity index 98% rename from packages/next/lib/page-loader.js rename to packages/next/client/page-loader.js index 5a07430f..0bf539b1 100644 --- a/packages/next/lib/page-loader.js +++ b/packages/next/client/page-loader.js @@ -1,5 +1,5 @@ /* global document */ -import EventEmitter from 'next-server/dist/lib/EventEmitter' +import EventEmitter from 'next-server/dist/lib/event-emitter' // smaller version of https://gist.github.com/igrigorik/a02f2359f3bc50ca7a9c function supportsPreload (list) { diff --git a/packages/next-server/lib/router/index.js b/packages/next/client/router.js similarity index 81% rename from packages/next-server/lib/router/index.js rename to packages/next/client/router.js index 63997d62..50ee28ac 100644 --- a/packages/next-server/lib/router/index.js +++ b/packages/next/client/router.js @@ -1,6 +1,5 @@ /* global window */ -import _Router from './router' -import { execOnce } from '../utils' +import _Router from 'next-server/dist/lib/router/router' const SingletonRouter = { router: null, // holds the actual router instance @@ -62,18 +61,6 @@ routerEvents.forEach((event) => { }) }) -const warnAboutRouterOnAppUpdated = execOnce(() => { - console.warn(`Router.onAppUpdated is removed - visit https://err.sh/zeit/next.js/no-on-app-updated-hook for more information.`) -}) - -Object.defineProperty(SingletonRouter, 'onAppUpdated', { - get () { return null }, - set () { - warnAboutRouterOnAppUpdated() - return null - } -}) - function throwIfNoRouter () { if (!SingletonRouter.router) { const message = 'No router instance found.\n' + @@ -86,7 +73,7 @@ function throwIfNoRouter () { export default SingletonRouter // Reexport the withRoute HOC -export { default as withRouter } from './with-router' +export { default as withRouter } from '../lib/with-router' // INTERNAL APIS // ------------- @@ -106,30 +93,6 @@ export const createRouter = function (...args) { // Export the actual Router class, which is usually used inside the server export const Router = _Router -export function _rewriteUrlForNextExport (url) { - const [, hash] = url.split('#') - url = url.replace(/#.*/, '') - - let [path, qs] = url.split('?') - path = path.replace(/\/$/, '') - - let newPath = path - // Append a trailing slash if this path does not have an extension - if (!/\.[^/]+\/?$/.test(path)) { - newPath = `${path}/` - } - - if (qs) { - newPath = `${newPath}?${qs}` - } - - if (hash) { - newPath = `${newPath}#${hash}` - } - - return newPath -} - // This function is used to create the `withRouter` router instance export function makePublicRouterInstance (router) { const instance = {} diff --git a/packages/next/client/webpack-hot-middleware-client.js b/packages/next/client/webpack-hot-middleware-client.js index fb0cece4..f967bdc8 100644 --- a/packages/next/client/webpack-hot-middleware-client.js +++ b/packages/next/client/webpack-hot-middleware-client.js @@ -1,6 +1,6 @@ import 'event-source-polyfill' import connect from './dev-error-overlay/hot-dev-client' -import Router from 'next-server/router' +import Router from 'next/router' const handlers = { reload (route) { diff --git a/packages/next/lib/constants.js b/packages/next/lib/constants.js index 2b7de225..ced263c8 100644 --- a/packages/next/lib/constants.js +++ b/packages/next/lib/constants.js @@ -3,3 +3,5 @@ export const NEXT_PROJECT_ROOT = join(__dirname, '..', '..') export const NEXT_PROJECT_ROOT_DIST = join(NEXT_PROJECT_ROOT, 'dist') export const NEXT_PROJECT_ROOT_NODE_MODULES = join(NEXT_PROJECT_ROOT, 'node_modules') export const DEFAULT_PAGES_DIR = join(NEXT_PROJECT_ROOT_DIST, 'pages') +export const NEXT_PROJECT_ROOT_DIST_CLIENT = join(NEXT_PROJECT_ROOT_DIST, 'client') +export const NEXT_PROJECT_ROOT_DIST_SERVER = join(NEXT_PROJECT_ROOT_DIST, 'server') diff --git a/packages/next-server/lib/router/with-router.js b/packages/next/lib/with-router.js similarity index 73% rename from packages/next-server/lib/router/with-router.js rename to packages/next/lib/with-router.js index 91fceca8..91544572 100644 --- a/packages/next-server/lib/router/with-router.js +++ b/packages/next/lib/with-router.js @@ -1,7 +1,7 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import hoistStatics from 'hoist-non-react-statics' -import { getDisplayName } from '../utils' +import { getDisplayName } from 'next-server/dist/lib/utils' export default function withRouter (ComposedComponent) { const displayName = getDisplayName(ComposedComponent) @@ -14,12 +14,10 @@ export default function withRouter (ComposedComponent) { static displayName = `withRouter(${displayName})` render () { - const props = { - router: this.context.router, - ...this.props - } - - return + return } } diff --git a/packages/next/link.js b/packages/next/link.js index db85d92e..6258ca0e 100644 --- a/packages/next/link.js +++ b/packages/next/link.js @@ -1 +1 @@ -module.exports = require('next-server/link') +export {default} from './dist/client/link' diff --git a/packages/next/package.json b/packages/next/package.json index 075ecfea..6961035c 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -32,7 +32,7 @@ }, "taskr": { "requires": [ - "./taskfile-babel.js" + "./taskfile-typescript.js" ] }, "dependencies": { @@ -60,6 +60,8 @@ "fresh": "0.5.2", "friendly-errors-webpack-plugin": "1.7.0", "glob": "7.1.2", + "hoist-non-react-statics": "3.2.0", + "htmlescape": "1.1.1", "http-status": "1.0.1", "launch-editor": "2.2.1", "loader-utils": "1.1.0", @@ -91,7 +93,8 @@ "@taskr/clear": "1.1.0", "@taskr/esnext": "1.1.0", "@taskr/watch": "1.1.0", - "taskr": "1.1.0" + "taskr": "1.1.0", + "typescript": "3.1.6" }, "engines": { "node": ">= 8.0.0" diff --git a/packages/next/pages/_app.js b/packages/next/pages/_app.js index d0e558bd..9ed64c4b 100644 --- a/packages/next/pages/_app.js +++ b/packages/next/pages/_app.js @@ -1,7 +1,7 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import { execOnce, loadGetInitialProps } from 'next-server/dist/lib/utils' -import { makePublicRouterInstance } from 'next-server/router' +import { makePublicRouterInstance } from 'next/router' export default class App extends Component { static childContextTypes = { diff --git a/packages/next/router.js b/packages/next/router.js index df64af7f..c0062ac6 100644 --- a/packages/next/router.js +++ b/packages/next/router.js @@ -1 +1,2 @@ -module.exports = require('next-server/router') +export * from './dist/client/router' +export {default} from './dist/client/router' diff --git a/packages/next-server/lib/error-debug.js b/packages/next/server/error-debug.js similarity index 100% rename from packages/next-server/lib/error-debug.js rename to packages/next/server/error-debug.js diff --git a/packages/next/taskfile-babel.js b/packages/next/taskfile-babel.js deleted file mode 100644 index c44e9a1c..00000000 --- a/packages/next/taskfile-babel.js +++ /dev/null @@ -1,65 +0,0 @@ -// taskr babel plugin with Babel 7 support -// https://github.com/lukeed/taskr/pull/305 -'use strict' - -const transform = require('@babel/core').transform -const flatten = require('flatten') - -const BABEL_REGEX = /(^@babel\/)(preset|plugin)-(.*)/i - -function getBabels () { - const pkg = require('./package.json') - return flatten( - ['devDependencies', 'dependencies'].map(s => Object.keys(pkg[s] || {})) - ).filter(s => BABEL_REGEX.test(s)) -} - -module.exports = function (task) { - let cache - - task.plugin('babel', {}, function * (file, opts) { - if (opts.preload) { - delete opts.preload - // get dependencies - cache = cache || getBabels() - - // attach any deps to babel's `opts` - cache.forEach(dep => { - const segs = BABEL_REGEX.exec(dep) - const type = `${segs[2]}s` - const name = `@babel/${segs[2]}-${segs[3]}` - - opts[type] = opts[type] || [] - - // flatten all (advanced entries are arrays) - if (flatten(opts[type]).indexOf(name) === -1) { - opts[type] = opts[type].concat(name) - } - }) - } - - // attach file's name - opts.filename = file.base - - const output = transform(file.data, opts) - - if (output.map) { - const map = `${file.base}.map` - - // append `sourceMappingURL` to original file - if (opts.sourceMaps !== 'both') { - output.code += Buffer.from(`\n//# sourceMappingURL=${map}`) - } - - // add sourcemap to `files` array - this._.files.push({ - base: map, - dir: file.dir, - data: Buffer.from(JSON.stringify(output.map)) - }) - } - - // update file's data - file.data = Buffer.from(output.code) - }) -} diff --git a/packages/next/taskfile-typescript.js b/packages/next/taskfile-typescript.js new file mode 100644 index 00000000..d5c02da1 --- /dev/null +++ b/packages/next/taskfile-typescript.js @@ -0,0 +1,43 @@ +'use strict' +try { + const ts = require('typescript') + const extname = require('path').extname + const config = require('./tsconfig.json') + + module.exports = function (task) { + task.plugin('typescript', { every: true }, function * (file, options) { + const opts = { + fileName: file.base, + compilerOptions: { + ...config.compilerOptions, + ...options + } + } + + const ext = extname(file.base) + // For example files without an extension don't have to be rewritten + if (ext) { + // Replace `.ts` with `.js` + const extRegex = new RegExp(ext.replace('.', '\\.') + '$', 'i') + file.base = file.base.replace(extRegex, '.js') + } + + // compile output + const result = ts.transpileModule(file.data.toString(), opts) + + if (opts.compilerOptions.sourceMap && result.sourceMapText) { + // add sourcemap to `files` array + this._.files.push({ + dir: file.dir, + base: `${file.base}.map`, + data: Buffer.from(JSON.stringify(result.sourceMapText), 'utf8') + }) + } + + // update file's data + file.data = Buffer.from(result.outputText, 'utf8') + }) + } +} catch (err) { + console.error(err) +} diff --git a/packages/next/taskfile.js b/packages/next/taskfile.js index 3e598bae..77e054bb 100644 --- a/packages/next/taskfile.js +++ b/packages/next/taskfile.js @@ -5,38 +5,38 @@ export async function compile (task) { } export async function bin (task, opts) { - await task.source(opts.src || 'bin/*').babel().target('dist/bin', {mode: '0755'}) + await task.source(opts.src || 'bin/*').typescript({module: 'commonjs'}).target('dist/bin', {mode: '0755'}) notify('Compiled binaries') } export async function lib (task, opts) { - await task.source(opts.src || 'lib/**/*.js').babel().target('dist/lib') + await task.source(opts.src || 'lib/**/*.js').typescript({module: 'commonjs'}).target('dist/lib') notify('Compiled lib files') } export async function server (task, opts) { - await task.source(opts.src || 'server/**/*.js').babel().target('dist/server') + await task.source(opts.src || 'server/**/*.js').typescript({module: 'commonjs'}).target('dist/server') notify('Compiled server files') } export async function nextbuild (task, opts) { - await task.source(opts.src || 'build/**/*.js').babel().target('dist/build') + await task.source(opts.src || 'build/**/*.js').typescript({module: 'commonjs'}).target('dist/build') notify('Compiled build files') } export async function client (task, opts) { - await task.source(opts.src || 'client/**/*.js').babel().target('dist/client') + await task.source(opts.src || 'client/**/*.js').typescript().target('dist/client') notify('Compiled client files') } // export is a reserved keyword for functions export async function nextbuildstatic (task, opts) { - await task.source(opts.src || 'export/**/*.js').babel().target('dist/export') + await task.source(opts.src || 'export/**/*.js').typescript({module: 'commonjs'}).target('dist/export') notify('Compiled export files') } export async function pages (task, opts) { - await task.source(opts.src || 'pages/**/*.js').babel().target('dist/pages') + await task.source(opts.src || 'pages/**/*.js').typescript().target('dist/pages') } export async function build (task) { diff --git a/packages/next/tsconfig.json b/packages/next/tsconfig.json new file mode 100644 index 00000000..1e365ba7 --- /dev/null +++ b/packages/next/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "allowJs": true, + "noEmit": true, + "module": "esnext", + "target": "ES2017", + "esModuleInterop": true, + "jsx": "react" + } +} diff --git a/test/integration/size-limit/next.config.js b/test/integration/size-limit/next.config.js index 35dcf0f6..ed457cd7 100644 --- a/test/integration/size-limit/next.config.js +++ b/test/integration/size-limit/next.config.js @@ -1,4 +1,13 @@ +const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer') module.exports = { + webpack (config, {isServer}) { + config.plugins.push(new BundleAnalyzerPlugin({ + analyzerMode: 'static', + reportFilename: `dist/${isServer ? 'server' : 'client'}.html`, + openAnalyzer: false + })) + return config + }, onDemandEntries: { // Make sure entries are not getting disposed. maxInactiveAge: 1000 * 60 * 60 diff --git a/test/integration/size-limit/test/index.test.js b/test/integration/size-limit/test/index.test.js index 657a3c15..f10e4978 100644 --- a/test/integration/size-limit/test/index.test.js +++ b/test/integration/size-limit/test/index.test.js @@ -66,6 +66,6 @@ describe('Production response size', () => { console.log(`Response Sizes:\n${responseSizes.map(obj => ` ${obj.url}: ${obj.bytes} (bytes)`).join('\n')} \nOverall: ${responseSizeKilobytes} KB`) // These numbers are without gzip compression! - expect(responseSizeKilobytes).toBeLessThanOrEqual(207) // Kilobytes + expect(responseSizeKilobytes).toBeLessThanOrEqual(210) // Kilobytes }) }) diff --git a/test/unit/EventEmitter.test.js b/test/unit/EventEmitter.test.js index 7dbef88c..73114fca 100644 --- a/test/unit/EventEmitter.test.js +++ b/test/unit/EventEmitter.test.js @@ -1,5 +1,5 @@ /* eslint-env jest */ -import EventEmitter from 'next-server/dist/lib/EventEmitter' +import EventEmitter from 'next-server/dist/lib/event-emitter' describe('EventEmitter', () => { describe('With listeners', () => { diff --git a/test/unit/shallow-equal.test.js b/test/unit/shallow-equal.test.js index 69e3a0a1..9f89f905 100644 --- a/test/unit/shallow-equal.test.js +++ b/test/unit/shallow-equal.test.js @@ -1,6 +1,6 @@ /* eslint-env jest */ -import shallowEquals from 'next-server/dist/lib/shallow-equals' +import shallowEquals from 'next-server/dist/lib/router/shallow-equals' describe('Shallow Equals', () => { it('should be true if both objects are the same', () => {