2017-07-16 10:18:21 +00:00
import { resolve , join , sep } from 'path'
2016-12-02 01:43:38 +00:00
import { createHash } from 'crypto'
2017-12-05 23:46:06 +00:00
import { realpathSync , existsSync } from 'fs'
2016-10-14 15:05:08 +00:00
import webpack from 'webpack'
import glob from 'glob-promise'
2016-10-15 19:49:42 +00:00
import WriteFilePlugin from 'write-file-webpack-plugin'
2016-12-03 22:01:15 +00:00
import FriendlyErrorsWebpackPlugin from 'friendly-errors-webpack-plugin'
2017-01-09 02:12:29 +00:00
import CaseSensitivePathPlugin from 'case-sensitive-paths-webpack-plugin'
2017-11-13 11:19:51 +00:00
import UglifyJSPlugin from 'uglifyjs-webpack-plugin'
2016-10-23 16:42:13 +00:00
import UnlinkFilePlugin from './plugins/unlink-file-plugin'
2017-04-03 18:10:24 +00:00
import PagesPlugin from './plugins/pages-plugin'
2017-04-17 20:15:50 +00:00
import DynamicChunksPlugin from './plugins/dynamic-chunks-plugin'
2017-03-24 07:51:34 +00:00
import CombineAssetsPlugin from './plugins/combine-assets-plugin'
2016-12-17 18:38:11 +00:00
import getConfig from '../config'
2017-01-31 06:31:27 +00:00
import * as babelCore from 'babel-core'
2017-02-12 21:18:22 +00:00
import findBabelConfig from './babel/find-config'
2017-02-16 02:07:29 +00:00
import rootModuleRelativePath from './root-module-relative-path'
2016-10-14 15:05:08 +00:00
2016-12-27 23:28:19 +00:00
const documentPage = join ( 'pages' , '_document.js' )
const defaultPages = [
'_error.js' ,
'_document.js'
]
2017-01-12 15:39:04 +00:00
const nextPagesDir = join ( _ _dirname , '..' , '..' , 'pages' )
const nextNodeModulesDir = join ( _ _dirname , '..' , '..' , '..' , 'node_modules' )
const interpolateNames = new Map ( defaultPages . map ( ( p ) => {
return [ join ( nextPagesDir , p ) , ` dist/pages/ ${ p } ` ]
} ) )
2016-12-27 23:28:19 +00:00
2017-02-16 02:07:29 +00:00
const relativeResolve = rootModuleRelativePath ( require )
2018-01-03 12:43:48 +00:00
async function getPages ( { dir , dev , pagesGlobPattern } ) {
let pages
if ( dev ) {
pages = await glob ( 'pages/+(_document|_error).+(js|jsx)' , { cwd : dir } )
} else {
pages = await glob ( pagesGlobPattern , { cwd : dir } )
}
return pages
}
function getPageEntries ( pages ) {
const entries = { }
for ( const p of pages ) {
entries [ join ( 'bundles' , p . replace ( '.jsx' , '.js' ) ) ] = [ ` ./ ${ p } ?entry ` ]
}
// The default pages (_document.js and _error.js) are only added when they're not provided by the user
for ( const p of defaultPages ) {
const entryName = join ( 'bundles' , 'pages' , p )
if ( ! entries [ entryName ] ) {
entries [ entryName ] = [ join ( nextPagesDir , p ) + '?entry' ]
}
}
return entries
}
2017-08-31 00:17:06 +00:00
export default async function createCompiler ( dir , { buildId , dev = false , quiet = false , buildDir , conf = null } = { } ) {
2018-01-03 12:43:48 +00:00
// Resolve relative path to absolute path
2017-11-05 19:05:15 +00:00
dir = realpathSync ( resolve ( dir ) )
2018-01-03 12:43:48 +00:00
// Used to track the amount of pages for webpack commons chunk plugin
let totalPages
// Loads next.config.js and custom configuration provided in custom server initialization
2017-06-23 03:48:06 +00:00
const config = getConfig ( dir , conf )
2018-01-03 12:43:48 +00:00
// Middlewares to handle on-demand entries and hot updates in development
const devEntries = dev ? [
2017-03-05 01:29:35 +00:00
join ( _ _dirname , '..' , '..' , 'client' , 'webpack-hot-middleware-client' ) ,
join ( _ _dirname , '..' , '..' , 'client' , 'on-demand-entries-client' )
] : [ ]
2017-01-12 15:39:04 +00:00
2018-01-03 12:43:48 +00:00
const mainJS = require . resolve ( ` ../../client/next ${ dev ? '-dev' : '' } ` ) // Uses client/next-dev in development for code splitting dev dependencies
2016-10-14 15:05:08 +00:00
2017-01-12 15:39:04 +00:00
const entry = async ( ) => {
2018-01-03 12:43:48 +00:00
// Get entries for pages in production mode. In development only _document and _error are added. Because pages are added by on-demand-entry-handler.
const pages = await getPages ( { dir , dev , pagesGlobPattern : config . pagesGlobPattern } )
const pageEntries = getPageEntries ( pages )
2016-10-19 12:41:45 +00:00
2018-01-03 12:43:48 +00:00
// Used for commons chunk calculations
totalPages = pages . length
if ( pages . indexOf ( documentPage ) !== - 1 ) {
totalPages = totalPages - 1
2016-12-27 23:28:19 +00:00
}
2016-10-14 15:05:08 +00:00
2018-01-03 12:43:48 +00:00
const entries = {
'main.js' : [
... devEntries , // Adds hot middleware and ondemand entries in development
... config . clientBootstrap || [ ] , // clientBootstrap can be used to load polyfills before code execution
mainJS // Main entrypoint in the client folder
] ,
... pageEntries
2017-01-12 15:39:04 +00:00
}
return entries
}
2016-10-14 15:05:08 +00:00
2016-10-16 02:49:09 +00:00
const plugins = [
2018-01-03 12:43:48 +00:00
// Defines NODE_ENV as development/production. This is used by some npm modules to determine if they should optimize.
new webpack . DefinePlugin ( {
'process.env.NODE_ENV' : JSON . stringify ( dev ? 'development' : 'production' )
} ) ,
new CaseSensitivePathPlugin ( ) , // Since on macOS the filesystem is case-insensitive this will make sure your path are case-sensitive
2017-05-04 20:44:25 +00:00
new webpack . IgnorePlugin ( /(precomputed)/ , /node_modules.+(elliptic)/ ) ,
2018-01-03 12:43:48 +00:00
// Provide legacy options to webpack
2016-12-28 18:16:52 +00:00
new webpack . LoaderOptionsPlugin ( {
options : {
context : dir ,
customInterpolateName ( url , name , opts ) {
return interpolateNames . get ( this . resourcePath ) || url
}
}
} ) ,
2018-01-03 12:43:48 +00:00
// Writes all generated files to disk, even in development. For SSR.
2016-10-18 14:14:00 +00:00
new WriteFilePlugin ( {
exitOnErrors : false ,
2016-10-23 16:42:13 +00:00
log : false ,
// required not to cache removed files
useHashIndex : false
2016-11-28 00:15:56 +00:00
} ) ,
2018-01-03 12:43:48 +00:00
// Moves common modules into commons.js
2016-11-28 00:15:56 +00:00
new webpack . optimize . CommonsChunkPlugin ( {
name : 'commons' ,
2016-12-06 09:25:01 +00:00
filename : 'commons.js' ,
2017-01-12 15:39:04 +00:00
minChunks ( module , count ) {
2017-07-16 10:18:21 +00:00
// We need to move react-dom explicitly into common chunks.
// Otherwise, if some other page or module uses it, it might
// included in that bundle too.
2018-01-03 12:43:48 +00:00
if ( dev && module . context && module . context . indexOf ( ` ${ sep } react ${ sep } ` ) >= 0 ) {
return true
}
if ( dev && module . context && module . context . indexOf ( ` ${ sep } react-dom ${ sep } ` ) >= 0 ) {
2017-07-16 10:18:21 +00:00
return true
}
2017-08-07 07:06:24 +00:00
// In the dev we use on-demand-entries.
// So, it makes no sense to use commonChunks based on the minChunks count.
// Instead, we move all the code in node_modules into each of the pages.
if ( dev ) {
return false
}
2017-06-21 04:27:24 +00:00
// If there are one or two pages, only move modules to common if they are
// used in all of the pages. Otherwise, move modules used in at-least
// 1/2 of the total pages into commons.
if ( totalPages <= 2 ) {
2017-07-01 07:24:16 +00:00
return count >= totalPages
2017-06-21 04:27:24 +00:00
}
2017-04-07 18:39:00 +00:00
return count >= totalPages * 0.5
2017-01-12 15:39:04 +00:00
}
2016-12-31 12:46:23 +00:00
} ) ,
2018-01-03 12:43:48 +00:00
// This chunk splits out react and react-dom in production to make sure it does not go through uglify. This saved multiple seconds on production builds.
// See https://twitter.com/dan_abramov/status/944040306420408325
new webpack . optimize . CommonsChunkPlugin ( {
name : 'react' ,
filename : 'react.js' ,
minChunks ( module , count ) {
if ( dev ) {
return false
}
if ( module . resource && module . resource . includes ( ` ${ sep } react-dom ${ sep } ` ) && count >= 0 ) {
return true
}
if ( module . resource && module . resource . includes ( ` ${ sep } react ${ sep } ` ) && count >= 0 ) {
return true
}
return false
}
} ) ,
2017-03-26 20:48:59 +00:00
// This chunk contains all the webpack related code. So, all the changes
// related to that happens to this chunk.
// It won't touch commons.js and that gives us much better re-build perf.
new webpack . optimize . CommonsChunkPlugin ( {
name : 'manifest' ,
filename : 'manifest.js'
} ) ,
2018-01-03 12:43:48 +00:00
// This adds Next.js route definitions to page bundles
2017-04-03 18:10:24 +00:00
new PagesPlugin ( ) ,
2018-01-03 12:43:48 +00:00
// Implements support for dynamic imports
new DynamicChunksPlugin ( )
2016-11-18 07:26:17 +00:00
]
2016-12-16 20:33:08 +00:00
if ( dev ) {
2016-11-18 07:26:17 +00:00
plugins . push (
new webpack . HotModuleReplacementPlugin ( ) ,
2017-01-12 15:39:04 +00:00
new webpack . NoEmitOnErrorsPlugin ( ) ,
2017-02-26 19:45:16 +00:00
new UnlinkFilePlugin ( )
2016-11-18 07:26:17 +00:00
)
2016-12-21 14:39:08 +00:00
if ( ! quiet ) {
plugins . push ( new FriendlyErrorsWebpackPlugin ( ) )
}
2016-12-16 20:33:08 +00:00
} else {
2017-07-01 07:24:16 +00:00
plugins . push ( new webpack . IgnorePlugin ( /react-hot-loader/ ) )
2016-12-16 20:33:08 +00:00
plugins . push (
2018-01-03 12:43:48 +00:00
// Minifies javascript bundles
2017-11-13 11:19:51 +00:00
new UglifyJSPlugin ( {
2018-01-03 12:43:48 +00:00
exclude : /react\.js/ ,
2017-11-13 11:19:51 +00:00
parallel : true ,
2017-11-28 13:19:14 +00:00
sourceMap : false ,
uglifyOptions : {
compress : {
comparisons : false
}
}
2016-12-16 20:33:08 +00:00
} )
)
2018-01-03 12:43:48 +00:00
plugins . push (
// Combines manifest.js commons.js and main.js into app.js in production
new CombineAssetsPlugin ( {
input : [ 'manifest.js' , 'react.js' , 'commons.js' , 'main.js' ] ,
output : 'app.js'
} ) ,
)
// Implements scope hoisting which speeds up browser execution of javascript
2017-07-17 16:46:39 +00:00
plugins . push ( new webpack . optimize . ModuleConcatenationPlugin ( ) )
2016-11-18 07:26:17 +00:00
}
2016-10-15 19:49:42 +00:00
2017-01-16 05:40:14 +00:00
const nodePathList = ( process . env . NODE _PATH || '' )
. split ( process . platform === 'win32' ? ';' : ':' )
. filter ( ( p ) => ! ! p )
2016-12-26 18:13:45 +00:00
const mainBabelOptions = {
2016-12-23 00:49:29 +00:00
cacheDirectory : true ,
2016-12-26 18:13:45 +00:00
presets : [ ]
2016-12-22 01:36:00 +00:00
}
2017-02-12 21:18:22 +00:00
const externalBabelConfig = findBabelConfig ( dir )
if ( externalBabelConfig ) {
2017-02-03 05:33:35 +00:00
console . log ( ` > Using external babel configuration ` )
2017-07-23 17:50:48 +00:00
console . log ( ` > Location: " ${ externalBabelConfig . loc } " ` )
2017-02-12 21:18:22 +00:00
// It's possible to turn off babelrc support via babelrc itself.
// In that case, we should add our default preset.
// That's why we need to do this.
const { options } = externalBabelConfig
mainBabelOptions . babelrc = options . babelrc !== false
2016-12-26 18:13:45 +00:00
} else {
2017-02-03 20:18:44 +00:00
mainBabelOptions . babelrc = false
2016-12-22 01:36:00 +00:00
}
2017-02-12 21:18:22 +00:00
// Add our default preset if the no "babelrc" found.
if ( ! mainBabelOptions . babelrc ) {
mainBabelOptions . presets . push ( require . resolve ( './babel/preset' ) )
}
2018-01-03 12:43:48 +00:00
const devLoaders = dev ? [ {
2017-12-05 23:46:06 +00:00
test : /\.(js|jsx)(\?[^?]*)?$/ ,
2016-12-01 22:46:49 +00:00
loader : 'hot-self-accept-loader' ,
include : [
join ( dir , 'pages' ) ,
nextPagesDir
]
2016-12-05 17:09:38 +00:00
} , {
2017-12-05 23:46:06 +00:00
test : /\.(js|jsx)(\?[^?]*)?$/ ,
2016-12-05 17:09:38 +00:00
loader : 'react-hot-loader/webpack' ,
exclude : /node_modules/
2018-01-03 12:43:48 +00:00
} ] : [ ]
const loaders = [ {
2016-12-01 22:46:49 +00:00
test : /\.json$/ ,
loader : 'json-loader'
} , {
2017-12-05 23:46:06 +00:00
test : /\.(js|jsx|json)(\?[^?]*)?$/ ,
2016-10-15 16:17:27 +00:00
loader : 'emit-file-loader' ,
2016-10-19 12:41:45 +00:00
include : [ dir , nextPagesDir ] ,
2016-10-25 09:11:39 +00:00
exclude ( str ) {
return /node_modules/ . test ( str ) && str . indexOf ( nextPagesDir ) !== 0
} ,
2016-12-28 18:16:52 +00:00
options : {
2017-01-31 06:31:27 +00:00
name : 'dist/[path][name].[ext]' ,
2017-12-05 23:46:06 +00:00
// We need to strip off .jsx on the server. Otherwise require without .jsx doesn't work.
interpolateName : ( name ) => name . replace ( '.jsx' , '.js' ) ,
validateFileName ( file ) {
const cases = [ { from : '.js' , to : '.jsx' } , { from : '.jsx' , to : '.js' } ]
for ( const item of cases ) {
const { from , to } = item
if ( file . slice ( - ( from . length ) ) !== from ) {
continue
}
const filePath = file . slice ( 0 , - ( from . length ) ) + to
if ( existsSync ( filePath ) ) {
throw new Error ( ` Both ${ from } and ${ to } file found. Please make sure you only have one of both. ` )
}
}
} ,
2017-01-31 06:31:27 +00:00
// By default, our babel config does not transpile ES2015 module syntax because
// webpack knows how to handle them. (That's how it can do tree-shaking)
// But Node.js doesn't know how to handle them. So, we have to transpile them here.
2017-02-01 06:17:15 +00:00
transform ( { content , sourceMap , interpolatedName } ) {
// Only handle .js files
2017-12-05 23:46:06 +00:00
if ( ! ( /\.(js|jsx)$/ . test ( interpolatedName ) ) ) {
2017-02-01 06:17:15 +00:00
return { content , sourceMap }
}
2017-02-16 02:07:29 +00:00
2017-01-31 06:31:27 +00:00
const transpiled = babelCore . transform ( content , {
2017-02-03 20:18:44 +00:00
babelrc : false ,
2017-01-31 06:31:27 +00:00
sourceMaps : dev ? 'both' : false ,
2017-02-02 23:58:02 +00:00
// Here we need to resolve all modules to the absolute paths.
2017-02-02 05:56:21 +00:00
// Earlier we did it with the babel-preset.
// But since we don't transpile ES2015 in the preset this is not resolving.
// That's why we need to do it here.
// See more: https://github.com/zeit/next.js/issues/951
plugins : [
2017-12-05 23:46:06 +00:00
require . resolve ( join ( _ _dirname , './babel/plugins/remove-dotjsx-from-import.js' ) ) ,
2017-02-03 20:18:44 +00:00
[ require . resolve ( 'babel-plugin-transform-es2015-modules-commonjs' ) ] ,
2017-02-02 05:56:21 +00:00
[
require . resolve ( 'babel-plugin-module-resolver' ) ,
{
alias : {
2018-01-08 15:44:52 +00:00
'babel-runtime' : relativeResolve ( 'babel-runtime/package' ) ,
2017-02-16 02:07:29 +00:00
'next/link' : relativeResolve ( '../../lib/link' ) ,
'next/prefetch' : relativeResolve ( '../../lib/prefetch' ) ,
'next/css' : relativeResolve ( '../../lib/css' ) ,
2017-04-17 20:15:50 +00:00
'next/dynamic' : relativeResolve ( '../../lib/dynamic' ) ,
2017-02-16 02:07:29 +00:00
'next/head' : relativeResolve ( '../../lib/head' ) ,
'next/document' : relativeResolve ( '../../server/document' ) ,
'next/router' : relativeResolve ( '../../lib/router' ) ,
'next/error' : relativeResolve ( '../../lib/error' ) ,
'styled-jsx/style' : relativeResolve ( 'styled-jsx/style' )
2017-02-02 05:56:21 +00:00
}
}
]
] ,
2017-01-31 06:31:27 +00:00
inputSourceMap : sourceMap
} )
2017-08-30 10:49:40 +00:00
// Strip ?entry to map back to filesystem and work with iTerm, etc.
let { map } = transpiled
let output = transpiled . code
if ( map ) {
2017-09-04 18:27:19 +00:00
let nodeMap = Object . assign ( { } , map )
nodeMap . sources = nodeMap . sources . map ( ( source ) => source . replace ( /\?entry/ , '' ) )
delete nodeMap . sourcesContent
2017-08-30 10:49:40 +00:00
// Output explicit inline source map that source-map-support can pickup via requireHook mode.
// Since these are not formal chunks, the devtool infrastructure in webpack does not output
// a source map for these files.
2017-09-04 18:27:19 +00:00
const sourceMapUrl = new Buffer ( JSON . stringify ( nodeMap ) , 'utf-8' ) . toString ( 'base64' )
2017-08-30 10:49:40 +00:00
output = ` ${ output } \n //# sourceMappingURL=data:application/json;charset=utf-8;base64, ${ sourceMapUrl } `
}
2017-01-31 06:31:27 +00:00
return {
2017-08-30 10:49:40 +00:00
content : output ,
2017-01-31 06:31:27 +00:00
sourceMap : transpiled . map
}
}
2016-10-15 16:17:27 +00:00
}
2016-12-01 22:46:49 +00:00
} , {
2016-12-28 18:16:52 +00:00
loader : 'babel-loader' ,
2016-11-05 16:12:21 +00:00
include : nextPagesDir ,
2017-01-12 01:57:33 +00:00
exclude ( str ) {
return /node_modules/ . test ( str ) && str . indexOf ( nextPagesDir ) !== 0
} ,
2016-12-28 18:16:52 +00:00
options : {
2016-12-11 09:09:54 +00:00
babelrc : false ,
2016-12-23 00:49:29 +00:00
cacheDirectory : true ,
2017-01-12 01:57:33 +00:00
presets : [ require . resolve ( './babel/preset' ) ]
2016-11-05 16:12:21 +00:00
}
} , {
2017-12-05 23:46:06 +00:00
test : /\.(js|jsx)(\?[^?]*)?$/ ,
2016-12-28 18:16:52 +00:00
loader : 'babel-loader' ,
2017-01-12 01:57:33 +00:00
include : [ dir ] ,
2016-10-25 09:11:39 +00:00
exclude ( str ) {
2017-01-12 01:57:33 +00:00
return /node_modules/ . test ( str )
2016-10-25 09:11:39 +00:00
} ,
2017-01-12 01:57:33 +00:00
options : mainBabelOptions
2018-01-03 12:43:48 +00:00
} ]
2016-10-19 12:41:45 +00:00
2016-12-17 18:38:11 +00:00
let webpackConfig = {
2016-10-14 15:05:08 +00:00
context : dir ,
entry ,
output : {
2017-04-07 16:52:12 +00:00
path : buildDir ? join ( buildDir , '.next' ) : join ( dir , config . distDir ) ,
2016-10-14 15:05:08 +00:00
filename : '[name]' ,
libraryTarget : 'commonjs2' ,
2017-12-27 18:59:17 +00:00
publicPath : ` /_next/webpack/ ` ,
2017-01-05 17:28:54 +00:00
strictModuleExceptionHandling : true ,
2016-12-02 01:43:38 +00:00
devtoolModuleFilenameTemplate ( { resourcePath } ) {
const hash = createHash ( 'sha1' )
hash . update ( Date . now ( ) + '' )
const id = hash . digest ( 'hex' ) . slice ( 0 , 7 )
// append hash id for cache busting
return ` webpack:/// ${ resourcePath } ? ${ id } `
2017-04-17 15:33:40 +00:00
} ,
// This saves chunks with the name given via require.ensure()
2017-12-27 18:59:17 +00:00
chunkFilename : '[name]-[chunkhash].js'
2016-10-14 15:05:08 +00:00
} ,
resolve : {
2018-01-03 12:43:48 +00:00
alias : {
// This bypasses React's check for production mode. Since we know it is in production this way.
// This allows us to exclude React from being uglified. Saving multiple seconds per build.
'react-dom' : dev ? 'react-dom/cjs/react-dom.development.js' : 'react-dom/cjs/react-dom.production.min.js'
} ,
2017-12-05 23:46:06 +00:00
extensions : [ '.js' , '.jsx' , '.json' ] ,
2016-12-28 18:16:52 +00:00
modules : [
2016-12-31 13:17:52 +00:00
nextNodeModulesDir ,
2017-01-16 05:40:14 +00:00
'node_modules' ,
... nodePathList
]
2016-10-14 15:05:08 +00:00
} ,
resolveLoader : {
2016-12-28 18:16:52 +00:00
modules : [
2016-12-31 13:17:52 +00:00
nextNodeModulesDir ,
2017-01-01 05:57:13 +00:00
'node_modules' ,
2017-01-16 05:40:14 +00:00
join ( _ _dirname , 'loaders' ) ,
... nodePathList
2016-10-14 15:05:08 +00:00
]
} ,
2016-10-15 19:49:42 +00:00
plugins ,
2016-10-14 15:05:08 +00:00
module : {
2018-01-03 12:43:48 +00:00
rules : [
... devLoaders ,
... loaders
]
2016-10-16 04:01:17 +00:00
} ,
2017-05-06 06:54:47 +00:00
devtool : dev ? 'cheap-module-inline-source-map' : false ,
2016-12-28 18:16:52 +00:00
performance : { hints : false }
2016-12-17 18:38:11 +00:00
}
2016-12-22 01:36:00 +00:00
2016-12-17 18:38:11 +00:00
if ( config . webpack ) {
2017-05-31 08:06:07 +00:00
console . log ( ` > Using "webpack" config function defined in ${ config . configOrigin } . ` )
2017-09-27 06:03:29 +00:00
webpackConfig = await config . webpack ( webpackConfig , { buildId , dev } )
2016-12-17 18:38:11 +00:00
}
return webpack ( webpackConfig )
2016-10-14 15:05:08 +00:00
}