Generate Pages.elm module from a Webpack plugin so it hooks into the lifecycle more smoothly.

This commit is contained in:
Dillon Kearns 2020-05-02 17:22:53 -07:00
parent 701e27953e
commit cb0f4cbed4
8 changed files with 267 additions and 117 deletions

View File

@ -27,10 +27,10 @@ module.exports = class AddFilesPlugin {
this.filesToGenerate = filesToGenerate;
}
apply(/** @type {webpack.Compiler} */ compiler) {
compiler.hooks.afterCompile.tapAsync("AddFilesPlugin", (compilation, callback) => {
const files = globby
.sync(["content/**/*.*"], {})
.map(unpackFile);
compiler.hooks.emit.tapAsync("AddFilesPlugin", (compilation, callback) => {
const files = globby.sync("content").map(unpackFile);
let staticRequestData = {}
@ -53,6 +53,7 @@ module.exports = class AddFilesPlugin {
const staticRequests = staticRequestData[route];
const filename = path.join(file.baseRoute, "content.json");
compilation.contextDependencies.add('content')
// compilation.fileDependencies.add(filename);
compilation.fileDependencies.add(path.resolve(file.filePath));
const rawContents = JSON.stringify({

View File

@ -1,4 +1,4 @@
const { compileToString } = require("../node-elm-compiler/index.js");
const { compileToStringSync } = require("../node-elm-compiler/index.js");
XMLHttpRequest = require("xhr2");
module.exports = runElm;
@ -8,29 +8,29 @@ function runElm(/** @type string */ mode) {
const mainElmFile = "../../src/Main.elm";
const startingDir = process.cwd();
process.chdir(elmBaseDirectory);
compileToString([mainElmFile], {}).then(function (data) {
(function () {
const warnOriginal = console.warn;
console.warn = function () { };
eval(data.toString());
const app = Elm.Main.init({
flags: { secrets: process.env, mode, staticHttpCache: global.staticHttpCache }
});
const data = compileToStringSync([mainElmFile], {});
process.chdir(startingDir);
(function () {
const warnOriginal = console.warn;
console.warn = function () { };
eval(data.toString());
const app = Elm.Main.init({
flags: { secrets: process.env, mode, staticHttpCache: global.staticHttpCache }
});
app.ports.toJsPort.subscribe(payload => {
process.chdir(startingDir);
app.ports.toJsPort.subscribe(payload => {
if (payload.tag === "Success") {
global.staticHttpCache = payload.args[0].staticHttpCache;
resolve(payload.args[0])
} else {
reject(payload.args[0])
}
delete Elm;
console.warn = warnOriginal;
});
})();
if (payload.tag === "Success") {
global.staticHttpCache = payload.args[0].staticHttpCache;
resolve(payload.args[0])
} else {
reject(payload.args[0])
}
delete Elm;
console.warn = warnOriginal;
});
})();
})
});
}

View File

@ -1,5 +1,4 @@
const webpack = require("webpack");
const middleware = require("webpack-dev-middleware");
const path = require("path");
const HTMLWebpackPlugin = require("html-webpack-plugin");
const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin');
@ -15,6 +14,7 @@ const express = require("express");
const ClosurePlugin = require("closure-webpack-plugin");
const readline = require("readline");
const webpackDevMiddleware = require("webpack-dev-middleware");
const PluginGenerateElmPagesBuild = require('./plugin-generate-elm-pages-build')
module.exports = { start, run };
function start({ routes, debug, customPort, manifestConfig, routesWithRequests, filesToGenerate }) {
@ -384,10 +384,11 @@ function webpackOptions(
"./index.js",
],
plugins: [
new PluginGenerateElmPagesBuild(),
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin(),
// Prevents compilation errors causing the hot loader to lose state
// new webpack.NoEmitOnErrorsPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
],
module: {
rules: [

View File

@ -6,7 +6,6 @@ const fs = require("fs");
const globby = require("globby");
const develop = require("./develop.js");
const chokidar = require("chokidar");
const doCliStuff = require("./generate-elm-stuff.js");
const { elmPagesUiFile } = require("./elm-file-constants.js");
const generateRecords = require("./generate-records.js");
const parseFrontmatter = require("./frontmatter.js");
@ -91,78 +90,91 @@ function run() {
const routes = toRoutes(markdownContent);
let resolvePageRequests;
let rejectPageRequests;
global.pagesWithRequests = new Promise(function (resolve, reject) {
resolvePageRequests = resolve;
rejectPageRequests = reject;
global.mode = contents.watch ? "dev" : "prod"
// global.pagesWithRequests = new Promise(function (resolve, reject) {
// resolvePageRequests = resolve;
// rejectPageRequests = reject;
// });
// resolvePageRequests({}); // TODO temporary - do the real resolution
develop.start({
routes,
debug: contents.debug,
manifestConfig: stubManifest,
routesWithRequests: {},
filesToGenerate: [],
customPort: contents.customPort
});
doCliStuff(
contents.watch ? "dev" : "prod",
staticRoutes,
markdownContent
).then(
function (payload) {
if (contents.watch) {
startWatchIfNeeded();
resolvePageRequests(payload.pages);
global.filesToGenerate = payload.filesToGenerate;
if (!devServerRunning) {
devServerRunning = true;
develop.start({
routes,
debug: contents.debug,
manifestConfig: payload.manifest,
routesWithRequests: payload.pages,
filesToGenerate: payload.filesToGenerate,
customPort: contents.customPort
});
}
} else {
if (payload.errors && payload.errors.length > 0) {
printErrorsAndExit(payload.errors);
}
// doCliStuff(
// contents.watch ? "dev" : "prod",
// staticRoutes,
// markdownContent
// ).then(
// function (payload) {
// if (contents.watch) {
// startWatchIfNeeded();
// resolvePageRequests(payload.pages);
// global.filesToGenerate = payload.filesToGenerate;
// if (!devServerRunning) {
// devServerRunning = true;
// develop.start({
// routes,
// debug: contents.debug,
// manifestConfig: payload.manifest,
// routesWithRequests: payload.pages,
// filesToGenerate: payload.filesToGenerate,
// customPort: contents.customPort
// });
// }
// } else {
// if (payload.errors && payload.errors.length > 0) {
// printErrorsAndExit(payload.errors);
// }
develop.run(
{
routes,
manifestConfig: payload.manifest,
routesWithRequests: payload.pages,
filesToGenerate: payload.filesToGenerate
},
() => { }
);
}
// develop.run(
// {
// routes,
// manifestConfig: payload.manifest,
// routesWithRequests: payload.pages,
// filesToGenerate: payload.filesToGenerate
// },
// () => { }
// );
// }
ensureDirSync("./gen");
// ensureDirSync("./gen");
// prevent compilation errors if migrating from previous elm-pages version
deleteIfExists("./gen/Pages/ContentCache.elm");
deleteIfExists("./gen/Pages/Platform.elm");
// // prevent compilation errors if migrating from previous elm-pages version
// deleteIfExists("./gen/Pages/ContentCache.elm");
// deleteIfExists("./gen/Pages/Platform.elm");
fs.writeFileSync(
"./gen/Pages.elm",
elmPagesUiFile(staticRoutes, markdownContent)
);
console.log("elm-pages DONE");
// fs.writeFileSync(
// "./gen/Pages.elm",
// elmPagesUiFile(staticRoutes, markdownContent)
// );
// console.log("elm-pages DONE");
}
).catch(function (errorPayload) {
startWatchIfNeeded()
resolvePageRequests({ type: 'error', message: errorPayload });
if (!devServerRunning) {
devServerRunning = true;
develop.start({
routes,
debug: contents.debug,
manifestConfig: stubManifest,
routesWithRequests: {},
filesToGenerate: [],
customPort: contents.customPort
});
}
// }
// ).catch(function (errorPayload) {
// startWatchIfNeeded()
// resolvePageRequests({ type: 'error', message: errorPayload });
// if (!devServerRunning) {
// devServerRunning = true;
// develop.start({
// routes,
// debug: contents.debug,
// manifestConfig: stubManifest,
// routesWithRequests: {},
// filesToGenerate: [],
// customPort: contents.customPort
// });
// }
});
// });
});
}

View File

@ -1,7 +1,7 @@
const fs = require("fs");
const runElm = require("./compile-elm.js");
const copyModifiedElmJson = require("./rewrite-elm-json.js");
const { elmPagesCliFile } = require("./elm-file-constants.js");
const { elmPagesCliFile, elmPagesUiFile } = require("./elm-file-constants.js");
const path = require("path");
const { ensureDirSync, deleteIfExists } = require('./file-helpers.js')
@ -19,6 +19,16 @@ module.exports = function run(
deleteIfExists("./elm-stuff/elm-pages/Pages/Platform.elm");
const uiFileContent = elmPagesUiFile(staticRoutes, markdownContent)
if (global.previousUiFileContent != uiFileContent) {
fs.writeFileSync(
"./gen/Pages.elm",
uiFileContent
);
}
global.previousUiFileContent = uiFileContent
// write `Pages.elm` with cli interface
fs.writeFileSync(
"./elm-stuff/elm-pages/Pages.elm",

View File

@ -0,0 +1,94 @@
const fs = require('fs')
const path = require('path')
const doCliStuff = require("./generate-elm-stuff.js");
const webpack = require('webpack')
const parseFrontmatter = require("./frontmatter.js");
const generateRecords = require("./generate-records.js");
const globby = require("globby");
module.exports = class PluginGenerateElmPagesBuild {
constructor() {
this.value = 1;
}
apply(/** @type {webpack.Compiler} */ compiler) {
compiler.hooks.afterEmit.tap('DoneMsg', () => {
console.log('---> DONE!');
})
compiler.hooks.beforeCompile.tap('PluginGenerateElmPagesBuild', (compilation) => {
// compiler.hooks.thisCompilation.tap('PluginGenerateElmPagesBuild', (compilation) => {
// compilation.contextDependencies.add('content')
// compiler.hooks.thisCompilation.tap('ThisCompilation', (compilation) => {
console.log('----> PluginGenerateElmPagesBuild');
const src = `module Example exposing (..)
value : Int
value = ${this.value++}
`
// console.log('@@@ Writing EXAMPLE module');
// fs.writeFileSync(path.join(process.cwd(), './src/Example.elm'), src);
const staticRoutes = generateRecords();
const markdownContent = globby
.sync(["content/**/*.*"], {})
.map(unpackFile)
.map(({ path, contents }) => {
return parseMarkdown(path, contents);
});
const images = globby
.sync("images/**/*", {})
.filter(imagePath => !fs.lstatSync(imagePath).isDirectory());
let resolvePageRequests;
let rejectPageRequests;
global.pagesWithRequests = new Promise(function (resolve, reject) {
resolvePageRequests = resolve;
rejectPageRequests = reject;
});
doCliStuff(
global.mode,
staticRoutes,
markdownContent
).then((payload) => {
console.log('PROMISE RESOLVED doCliStuff');
resolvePageRequests(payload.pages);
global.filesToGenerate = payload.filesToGenerate;
}).catch(function (errorPayload) {
resolvePageRequests({ type: 'error', message: errorPayload });
})
// compilation.assets['./src/Example.elm'] = {
// source: () => src,
// size: () => src.length
// };
// callback()
});
};
}
function unpackFile(path) {
return { path, contents: fs.readFileSync(path).toString() };
}
function parseMarkdown(path, fileContents) {
const { content, data } = parseFrontmatter(path, fileContents);
return {
path,
metadata: JSON.stringify(data),
body: content,
extension: "md"
};
}

View File

@ -72,6 +72,20 @@ function loadContentAndInitializeApp(/** @type { init: any } */ mainElmModule)
});
if (module.hot) {
module.hot.addStatusHandler(function (status) {
console.log('HMR', status)
if (status === 'idle') {
// httpGet(`${window.location.origin}${path}content.json`).then(function (/** @type JSON */ contentJson) {
// // console.log('hot contentJson', contentJson);
// app.ports.fromJsPort.send({ contentJson: contentJson });
// });
// console.log('Reloaded!!!!!!!!!!', status)
}
});
}
// found this trick from https://github.com/roots/sage/issues/1826
@ -81,18 +95,17 @@ function loadContentAndInitializeApp(/** @type { init: any } */ mainElmModule)
const success = reporter.success
reporter.success = function () {
console.log('SUCCESS');
app.ports.fromJsPort.send({});
success()
}
httpGet(`${window.location.origin}${path}content.json`).then(function (/** @type JSON */ contentJson) {
// console.log('hot contentJson', contentJson);
if (module.hot) {
module.hot.addStatusHandler(function (status) {
if (status === 'idle') {
console.log('Reloaded!!!!!!!!!!', status)
}
setTimeout(() => {
app.ports.fromJsPort.send({ contentJson: contentJson });
}, 0)
success()
});
}
return app
});

View File

@ -223,6 +223,13 @@ type alias ContentJson =
}
contentJsonDecoder : Decode.Decoder ContentJson
contentJsonDecoder =
Decode.map2 ContentJson
(Decode.field "body" Decode.string)
(Decode.field "staticData" (Decode.dict Decode.string))
init :
pathKey
-> String
@ -272,12 +279,6 @@ init pathKey canonicalSiteUrl document toJsPort viewFn content initUserModel fla
|> Decode.decodeValue (Decode.field "contentJson" contentJsonDecoder)
|> Result.toMaybe
contentJsonDecoder : Decode.Decoder ContentJson
contentJsonDecoder =
Decode.map2 ContentJson
(Decode.field "body" Decode.string)
(Decode.field "staticData" (Decode.dict Decode.string))
baseUrl =
flags
|> Decode.decodeValue (Decode.field "baseUrl" Decode.string)
@ -390,7 +391,7 @@ type AppMsg userMsg metadata view
| UpdateCacheAndUrl Url (Result Http.Error (ContentCache metadata view))
| UpdateCacheForHotReload (Result Http.Error (ContentCache metadata view))
| PageScrollComplete
| HotReloadComplete
| HotReloadComplete ContentJson
type Model userModel userMsg metadata view
@ -592,14 +593,19 @@ update content allRoutes canonicalSiteUrl viewFunction pathKey onPageChangeMsg t
PageScrollComplete ->
( model, Cmd.none )
HotReloadComplete ->
( model
, ContentCache.init document content (Maybe.map (\cj -> { contentJson = cj, initialUrl = model.url }) Nothing)
|> ContentCache.lazyLoad document
{ currentUrl = model.url
, baseUrl = model.baseUrl
}
|> Task.attempt UpdateCacheForHotReload
HotReloadComplete contentJson ->
let
_ =
Debug.log "HotReloadComplete" { keys = Dict.keys contentJson.staticData, url = model.url.path }
in
( { model | contentCache = ContentCache.init document content (Just { contentJson = contentJson, initialUrl = model.url }) }
, Cmd.none
-- ContentCache.init document content (Maybe.map (\cj -> { contentJson = contentJson, initialUrl = model.url }) Nothing)
--|> ContentCache.lazyLoad document
-- { currentUrl = model.url
-- , baseUrl = model.baseUrl
-- }
--|> Task.attempt UpdateCacheForHotReload
)
CliMsg _ ->
@ -709,7 +715,20 @@ application config =
[ config.subscriptions model.userModel
|> Sub.map UserMsg
|> Sub.map AppMsg
, config.fromJsPort |> Sub.map (\_ -> AppMsg HotReloadComplete)
, config.fromJsPort
|> Sub.map
(\decodeValue ->
--let
-- _ =
-- Debug.log "fromJsPort" (decodeValue |> Decode.decodeValue (Decode.field "contentJson" contentJsonDecoder))
--in
case decodeValue |> Decode.decodeValue (Decode.field "contentJson" contentJsonDecoder) of
Ok contentJson ->
AppMsg (HotReloadComplete contentJson)
Err error ->
Debug.todo ""
)
]
CliModel _ ->