Skip to content

Commit

Permalink
Add gatsby-module-loader to replace bundle-loader to add ability to d…
Browse files Browse the repository at this point in the history
…elay executing scripts after loading
  • Loading branch information
KyleAMathews committed Jun 8, 2017
1 parent 0c8abd7 commit 69f60ae
Show file tree
Hide file tree
Showing 11 changed files with 212 additions and 22 deletions.
23 changes: 16 additions & 7 deletions packages/gatsby/src/cache-dir/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ const sortPagesByCount = (a, b) => {
}
}

const prefetcher = () => {}

const queue = {
empty: () => {
pathArray = []
Expand All @@ -46,7 +48,6 @@ const queue = {
resourcesArray = []
pages = []
},
// Make pages injectable cause testing.
addPagesArray: newPages => {
pages = newPages
findPage = pageFinderFactor(newPages)
Expand Down Expand Up @@ -144,6 +145,7 @@ const queue = {
console.log("need to load scripts")
const page = findPage(path)
console.log("for page", page)
console.log(`async requires`, asyncRequires)
let component
let json
const done = () => {
Expand All @@ -155,12 +157,19 @@ const queue = {
})
}
}
asyncRequires.components[page.componentChunkName](c => {
component = preferDefault(c)
done()
})
asyncRequires.json[page.jsonName](j => {
json = preferDefault(j)
console.log(asyncRequires.components[page.componentChunkName])
console.log(asyncRequires.json[page.jsonName])
asyncRequires.components[page.componentChunkName]()(
callback => {
component = preferDefault(callback())
console.log(`page component`, component)
done()
},
() => console.log("error loading page component")
)
asyncRequires.json[page.jsonName]()(callback => {
json = preferDefault(callback())
console.log(`json`, json)
done()
})
}
Expand Down
11 changes: 6 additions & 5 deletions packages/gatsby/src/cache-dir/production-app.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ window.___emitter = mitt()

import pages from "./pages.json"
import ComponentRenderer from "./component-renderer"
import requires from "./async-requires"
import asyncRequires from "./async-requires"
import loader from "./loader"
loader.addPagesArray(pages)
loader.addProdRequires(requires)
loader.addProdRequires(asyncRequires)
window.asyncRequires = asyncRequires

window.___loader = loader

Expand Down Expand Up @@ -185,9 +186,9 @@ let layout
// }

const loadLayout = cb => {
console.log(requires)
if (requires.layouts[`index`]) {
requires.layouts[`index`](cb)
console.log(asyncRequires)
if (asyncRequires.layouts[`index`]) {
asyncRequires.layouts[`index`](cb)
} else {
cb(props => <div>{props.children()}</div>)
}
Expand Down
12 changes: 6 additions & 6 deletions packages/gatsby/src/cache-dir/static-entry.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from "react"
import { renderToString, renderToStaticMarkup } from "react-dom/server"
import { StaticRouter, Route, withRouter } from "react-router-dom"
import { kebabCase, get, merge, isArray } from "lodash"
import { kebabCase, get, merge, isArray, isString } from "lodash"
import apiRunner from "./api-runner-ssr"
import pages from "./pages.json"
import syncRequires from "./sync-requires"
Expand Down Expand Up @@ -149,19 +149,19 @@ module.exports = (locals, callback) => {
if (prefixedScript === `/`) {
return
}

return prefixedScript
})
.filter(s => s)
.filter(s => isString(s))

scripts.forEach(script => {
// Add preload <link>s for scripts.
headComponents.unshift(
<link rel="preload" href={prefixedScript} as="script" />
)
headComponents.unshift(<link rel="preload" href={script} as="script" />)
})

// Add script loader for page scripts to the head.
// Taken from https://www.html5rocks.com/en/tutorials/speed/script-loading/
const scriptsString = scripts.map(s => `"${prefixedScript}"`).join(`,`)
const scriptsString = scripts.map(s => `"${s}"`).join(`,`)
headComponents.push(
<script
dangerouslySetInnerHTML={{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ const preferDefault = m => m && m.default || m
asyncRequires += `exports.components = {\n${components
.map(
c =>
` "${c.componentChunkName}": require("bundle-loader?lazy&name=${c.componentChunkName}!${joinPath(
` "${c.componentChunkName}": require("gatsby-module-loader?name=${c.componentChunkName}!${joinPath(
c.component
)}")`
)
Expand All @@ -116,7 +116,7 @@ const preferDefault = m => m && m.default || m
asyncRequires += `exports.json = {\n${json
.map(
j =>
` "${j.jsonName}": require("bundle-loader?lazy&name=${pathChunkName(
` "${j.jsonName}": require("gatsby-module-loader?name=${pathChunkName(
j.path
)}!${joinPath(program.directory, `/.cache/json/`, j.jsonName)}")`
)
Expand All @@ -127,7 +127,7 @@ const preferDefault = m => m && m.default || m
let componentName = layout
if (layout !== false || typeof layout !== `undefined`) {
componentName = `index`
return ` "${layout}": require("bundle-loader?lazy&name=${`layout-component---${layout}`}!${joinPath(
return ` "${layout}": require("gatsby-module-loader?name=${`layout-component---${layout}`}!${joinPath(
program.directory,
`/src/layouts/`,
componentName
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
5 changes: 5 additions & 0 deletions packages/gatsby/src/loaders/gatsby-module-loader/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# `gatsby-module-loader` for webpack

_Based on https://github.com/webpack/bundle-loader and https://github.com/NekR/async-module-loader_

Is lazy and chunk execution is always deferred + there's good error handling.
58 changes: 58 additions & 0 deletions packages/gatsby/src/loaders/gatsby-module-loader/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
Based on Tobias Koppers @sokra bundle-loader
https://github.com/webpack/bundle-loader
and Arthur Stolyar's async-module-loader
*/
const loaderUtils = require(`loader-utils`)
const path = require(`path`)
console.log("in gatsby-module-loader")

module.exports = function() {}
module.exports.pitch = function(remainingRequest) {
this.cacheable && this.cacheable()

console.log("this.query", this.query)
const query = loaderUtils.parseQuery(this.query)
console.log("query", query)
let chunkName = ``

if (query.name) {
chunkName = loaderUtils.interpolateName(this, query.name, {
context: query.context,
regExp: query.regExp,
})
chunkName = `, ${JSON.stringify(chunkName)}`
}

console.log(`chunkName`, chunkName)

const request = loaderUtils.stringifyRequest(this, `!!` + remainingRequest)

const callback = "callback(function() { return require(" + request + ") })"

const executor = `
return function(callback, errback) {
require.ensure([], function(_, error) {
if (error) {
errback()
} else {
${callback}
}
}${chunkName});
}`

const result = `
require(
${loaderUtils.stringifyRequest(
this,
`!${path.join(__dirname, `patch.js`)}`
)}
);
module.exports = function() { ${executor} }
`

console.log(`result`, result)

return result
}
11 changes: 11 additions & 0 deletions packages/gatsby/src/loaders/gatsby-module-loader/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "gatsby-module-loader",
"version": "1.0.0",
"description": "_Based on https://github.com/webpack/bundle-loader and https://github.com/NekR/async-module-loader_",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "MIT"
}
80 changes: 80 additions & 0 deletions packages/gatsby/src/loaders/gatsby-module-loader/patch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
patch()

function patch() {
var head = document.querySelector(`head`)
var ensure = __webpack_require__.e
var chunks = __webpack_require__.s
var failures

__webpack_require__.e = function(chunkId, callback) {
var loaded = false
var immediate = true

var handler = function(error) {
if (!callback) return

callback(__webpack_require__, error)
callback = null
}

if (!chunks && failures && failures[chunkId]) {
handler(true)
return
}

ensure(chunkId, function() {
if (loaded) return
loaded = true

if (immediate) {
// webpack fires callback immediately if chunk was already loaded
// IE also fires callback immediately if script was already
// in a cache (AppCache counts too)
setTimeout(function() {
handler()
})
} else {
handler()
}
})

// This is |true| if chunk is already loaded and does not need onError call.
// This happens because in such case ensure() is performed in sync way
if (loaded) {
return
}

immediate = false

onError(function() {
if (loaded) return
loaded = true

if (chunks) {
chunks[chunkId] = void 0
} else {
failures || (failures = {})
failures[chunkId] = true
}

handler(true)
})
}

function onError(callback) {
var script = head.lastChild

if (script.tagName !== `SCRIPT`) {
if (typeof console !== `undefined` && console.warn) {
console.warn(`Script is not a script`, script)
}

return
}

script.onload = script.onerror = function() {
script.onload = script.onerror = null
setTimeout(callback, 0)
}
};
};
23 changes: 23 additions & 0 deletions packages/gatsby/src/loaders/gatsby-module-loader/plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module.exports = function() {}
module.exports.prototype.apply = function(compiler) {
compiler.plugin(`compilation`, function(compilation) {
compilation.mainTemplate.plugin(`require-extensions`, function(
source,
chunk,
hash
) {
if (chunk.chunks.length > 0) {
var buf = []

buf.push(``)
buf.push(``)
buf.push(`// expose the chunks object`)
buf.push(this.requireFn + `.s = installedChunks;`)

return source + this.asString(buf)
}

return source
})
})
}
4 changes: 3 additions & 1 deletion packages/gatsby/src/utils/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const { store } = require(`../redux`)
const debug = require(`debug`)(`gatsby:webpack-config`)
const WebpackMD5Hash = require(`webpack-md5-hash`)
const ChunkManifestPlugin = require(`chunk-manifest-webpack-plugin`)
const GatsbyModulePlugin = require(`../loaders/gatsby-module-loader/plugin`)
const genBabelConfig = require(`./babel-config`)

// Five stages or modes:
Expand Down Expand Up @@ -268,6 +269,7 @@ module.exports = async (
}),
// Ensure module order stays the same. Supposibly fixed in webpack 2.0.
new webpack.optimize.OccurenceOrderPlugin(),
new GatsbyModulePlugin(),
// new WebpackStableModuleIdAndHash({ seed: 9, hashSize: 47 }),
new webpack.NamedModulesPlugin(),
]
Expand Down Expand Up @@ -469,7 +471,7 @@ module.exports = async (

return {
root,
modulesDirectories: [`node_modules`],
modulesDirectories: [path.join(__dirname, `../loaders`), `node_modules`],
}
}

Expand Down

0 comments on commit 69f60ae

Please sign in to comment.