Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Admin UI static building #1088

Merged
merged 32 commits into from
May 8, 2019
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
11e3121
Static build proof of concept
emmatown Mar 13, 2019
5a87cbf
Merge branch 'master' into static-build-poc
emmatown May 3, 2019
af4b4a1
It's working but not ready yet
emmatown May 3, 2019
6c39e5b
Get it actually working
emmatown May 3, 2019
877a245
Get fallback routing working
emmatown May 3, 2019
a418559
Get static building with auth working
emmatown May 3, 2019
b859934
Try a thing for heroku
emmatown May 3, 2019
796eeba
Add process.env.NODE_ENV = 'production' to build command
emmatown May 3, 2019
a102692
Try another thing
emmatown May 3, 2019
98c488b
Try another thing
emmatown May 3, 2019
07115bf
Try another thing
emmatown May 3, 2019
6cb55ff
Try another thing
emmatown May 3, 2019
6608677
Use mongo URI from env variable
emmatown May 4, 2019
4e1da89
More stuff
emmatown May 5, 2019
05699e5
Implement --out and distDir
emmatown May 7, 2019
d13bdb7
Merge branch 'master' into static-build-poc
emmatown May 7, 2019
9247ae0
Deduplicate a util
emmatown May 7, 2019
cfe8dfc
Merge branch 'static-build-poc' of https://github.com/keystonejs/keys…
emmatown May 7, 2019
501382a
Add changesets
emmatown May 7, 2019
d49e2ab
Merge branch 'master' into static-build-poc
emmatown May 8, 2019
650c30b
Update packages/admin-ui/server/AdminUI.js
jesstelford May 8, 2019
a13f6c8
Stuff
emmatown May 8, 2019
21f6208
Remove a thing
emmatown May 8, 2019
8968ff7
Fix some stuff
emmatown May 8, 2019
e5a4847
Update packages/keystone/bin/commands/build.js
jesstelford May 8, 2019
6c00031
Update docs to be accurate to what is currently implemented and make …
emmatown May 8, 2019
a24d5df
Prettier
emmatown May 8, 2019
de37634
Merge branch 'master' into static-build-poc
emmatown May 8, 2019
d3f31ad
Fix the cypress:run:ci script for the login tests
emmatown May 8, 2019
cf8a5bc
Update packages/keystone/bin/commands/dev.js
jesstelford May 8, 2019
63197dd
Add DEFAULT_DIST_DIR
emmatown May 8, 2019
1d7583a
Re-export DEFAULT_DIST_DIR to fix a bug
emmatown May 8, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .changeset/2738beb1/changes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "releases": [{ "name": "@keystone-alpha/keystone", "type": "minor" }], "dependents": [] }
1 change: 1 addition & 0 deletions .changeset/2738beb1/changes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Add `build` and `start` commands
10 changes: 10 additions & 0 deletions .changeset/f951ae8b/changes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"releases": [
{ "name": "@keystone-alpha/admin-ui", "type": "minor" },
{ "name": "@keystone-alpha/keystone", "type": "minor" },
{ "name": "@keystone-alpha/server", "type": "minor" },
{ "name": "@keystone-alpha/demo-project-blog", "type": "patch" },
{ "name": "@keystone-alpha/demo-project-meetup", "type": "patch" }
],
"dependents": []
}
1 change: 1 addition & 0 deletions .changeset/f951ae8b/changes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Add Admin UI static building
2 changes: 1 addition & 1 deletion Procfile
Original file line number Diff line number Diff line change
@@ -1 +1 @@
web: yarn start:test
web: cd demo-projects/blog && node node_modules/@keystone-alpha/keystone/bin/cli
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is strange, Heroku was being weird and this fixed it. I think it's only because of bolt monorepo reasons so it won't cause an issue for consumers

24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,35 +205,34 @@ For available options, see [Server Configuration](#server-configuration).
When getting ready to deploy your app to production, there are performance
optimisations which Keystone can prepare for you.

Add this script to your `package.json`:
Add these scripts to your `package.json`:

```json
{
"scripts": {
"build": "keystone build"
"build": "keystone build",
"start": "keystone start"
}
}
```

Run `npm run build` to generate the following outputs:
Run `npm run build` to generate the following outputs(this output could change in the future):

```
.
└── dist/
├── api/
├── admin/
└── index.js
└── admin/
```

To run your keystone instance, execute the `index.js` file:
To run your keystone instance, run the start script.

```
cd dist
node index.js
npm run start
```

#### Production Build Artifacts

<!--
##### `dist/index.js`

An all-in-one server which will start your Keystone API and Admin UI running on
Expand All @@ -250,14 +249,15 @@ the keystone instance, and a server to run the API.
This folder contains an `index.js` file which when run via node
(`node dist/api/index.js`) will serve the API. In this manner, it is possible to
deploy the API independently of the [admin UI](#distadmin) by deploying the
contents of the `dist/api/` folder only.
contents of the `dist/api/` folder only. -->

##### `dist/admin/`

A static export of the Admin UI lives here. Built from your code setting up the
keystone instance, this export contains _list_ and _field_ config information
tightly coupled to the API. It is therefore recommended to always deploy the
Admin UI at the same time as deploying the API to avoid any inconsistencies.
tightly coupled to the API.

<!-- commented out for now because you currently have to deploy them at the same time right now: It is therefore recommended to always deploy the Admin UI at the same time as deploying the API to avoid any inconsistencies. -->

### Adding Authentication

Expand Down
2 changes: 1 addition & 1 deletion demo-projects/blog/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
},
"scripts": {
"start": "DISABLE_LOGGING=true node server.js",
"build": "next build"
"build": "next build app && keystone build"
},
"dependencies": {
"@arch-ui/layout": "^0.2.0",
Expand Down
2 changes: 1 addition & 1 deletion demo-projects/blog/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const nextApp = next({

Promise.all([keystone.prepare({ port }), nextApp.prepare()])
.then(async ([{ server, keystone: keystoneApp }]) => {
await keystoneApp.connect();
await keystoneApp.connect(process.env.MONGODB_URI);
// Initialise some data.
// NOTE: This is only for demo purposes and should not be used in production
const users = await keystoneApp.lists.User.adapter.findAll();
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"contributing-guide": "is-ci && exit 0 || chalk -t \"\n\n{bold 📝 Contributing to KeystoneJS?}\n\" && link '🔗 Read the full Contributing Guide' 'https://github.com/keystonejs/keystone-5/blob/master/CONTRIBUTING.md' && echo \"\n\"",
"contributors:add": "all-contributors add",
"contributors:generate": "all-contributors generate",
"heroku-postbuild": "bolt && yarn build"
"heroku-postbuild": "bolt && yarn build && bolt w @keystone-alpha/demo-project-blog build"
},
"dependencies": {
"@babel/cli": "^7.1.2",
Expand Down Expand Up @@ -99,6 +99,7 @@
"endent": "^1.3.0",
"ensure-error": "^1.0.0",
"express": "^4.16.3",
"express-history-api-fallback": "^2.2.1",
"express-pino-logger": "^4.0.0",
"express-react-views": "^0.11.0",
"express-session": "^1.15.6",
Expand Down
3 changes: 2 additions & 1 deletion packages/admin-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"compression": "^1.7.4",
"cross-fetch": "^2.2.0",
"express": "^4.16.3",
"express-history-api-fallback": "^2.2.1",
"falsey": "^1.0.0",
"file-loader": "^3.0.1",
"graphql": "^14.0.2",
Expand Down Expand Up @@ -92,4 +93,4 @@
"webpack-dev-middleware": "^3.6.1",
"webpack-hot-middleware": "^2.24.3"
}
}
}
145 changes: 108 additions & 37 deletions packages/admin-ui/server/AdminUI.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');
const compression = require('compression');
const { createSessionMiddleware } = require('@keystone-alpha/session');
const path = require('path');
const fs = require('fs');
const fallback = require('express-history-api-fallback');

const pkgInfo = require('../package.json');

const getWebpackConfig = require('./getWebpackConfig');
const { mode } = require('./env');

module.exports = class AdminUI {
constructor(keystone, config = {}) {
Expand Down Expand Up @@ -60,6 +62,104 @@ module.exports = class AdminUI {
);
}

staticBuild({ distDir, apiPath, graphiqlPath }) {
const builtAdminRoot = path.join(distDir, 'admin');

const adminMeta = this.getAdminUIMeta({ apiPath, graphiqlPath });

const compilers = [];

const secureCompiler = webpack(
getWebpackConfig({
adminMeta,
entry: 'index',
outputPath: path.join(builtAdminRoot, 'secure'),
})
);
compilers.push(secureCompiler);

if (this.authStrategy) {
const publicCompiler = webpack(
getWebpackConfig({
// override lists so that schema and field views are excluded
adminMeta: { ...adminMeta, lists: {} },
entry: 'public',
outputPath: path.join(builtAdminRoot, 'public'),
})
);
compilers.push(publicCompiler);
}

return Promise.all(
compilers.map(
compiler =>
new Promise((resolve, reject) => {
compiler.run(err => {
if (err) {
reject(err);
} else {
resolve();
}
});
})
)
);
}

getAdminUIMeta({ apiPath, graphiqlPath }) {
const { adminPath } = this;

return {
adminPath,
apiPath,
graphiqlPath,
...this.getAdminMeta(),
...this.keystone.getAdminMeta(),
};
}

createProdMiddleware({ distDir }) {
const app = express.Router();

app.use(compression());

const builtAdminRoot = path.join(distDir, 'admin');
if (!fs.existsSync(builtAdminRoot)) {
throw new Error(
'There are no Admin UI build artifacts. Please run `keystone build` before running `keystone start`'
);
}
const secureBuiltRoot = path.join(builtAdminRoot, 'secure');
const secureStaticMiddleware = express.static(secureBuiltRoot);
const secureFallbackMiddleware = fallback('index.html', { root: secureBuiltRoot });

if (this.authStrategy) {
const publicBuiltRoot = path.join(builtAdminRoot, 'public');
const publicStaticMiddleware = express.static(publicBuiltRoot);
const publicFallbackMiddleware = fallback('index.html', { root: publicBuiltRoot });
app.use((req, res, next) => {
// TODO: Better security, should check some property of the user
return req.user
? secureStaticMiddleware(req, res, next)
: publicStaticMiddleware(req, res, next);
});

app.use((req, res, next) => {
// TODO: Better security, should check some property of the user
return req.user
? secureFallbackMiddleware(req, res, next)
: publicFallbackMiddleware(req, res, next);
});
} else {
app.use(secureStaticMiddleware);
app.use(secureFallbackMiddleware);
}

const _app = express().Router();
_app.use('/admin', app);
return _app;
}

createDevMiddleware({ apiPath, graphiqlPath, port }) {
const app = express();
const { adminPath } = this;
Expand All @@ -73,12 +173,6 @@ module.exports = class AdminUI {
console.log(`🔗 ${chalk.green('Keystone Admin UI:')} ${clickableUrl} (v${pkgInfo.version})`);
}

if (mode === 'production') {
// only use compression in production because it breaks server sent events
// which is what webpack-hot-middleware uses
app.use(compression());
}

app.use(adminPath, (req, res, next) => {
// TODO: make sure that this change is OK. (regex was testing on url, not path)
// Changed because this was preventing adminui pages loading when a querystrings
Expand All @@ -88,13 +182,9 @@ module.exports = class AdminUI {
});

// add the webpack dev middleware
const adminMeta = {
adminPath,
apiPath,
graphiqlPath,
...this.getAdminMeta(),
...this.keystone.getAdminMeta(),
};

let adminMeta = this.getAdminUIMeta({ apiPath, graphiqlPath });

const webpackMiddlewareConfig = {
publicPath: adminPath,
stats: 'minimal',
Expand Down Expand Up @@ -128,32 +218,13 @@ module.exports = class AdminUI {
return req.user ? secureMiddleware(req, res, next) : publicMiddleware(req, res, next);
});

if (mode === 'development') {
app.use((req, res, next) => {
return req.user
? secureHotMiddleware(req, res, next)
: publicHotMiddleware(req, res, next);
});
}

this.stopDevServer = () => {
return new Promise(resolve => {
publicMiddleware.close(() => {
secureMiddleware.close(resolve);
});
});
};
app.use((req, res, next) => {
return req.user ? secureHotMiddleware(req, res, next) : publicHotMiddleware(req, res, next);
});
} else {
// No auth required? Everyone can access the "secure" area
app.use(secureMiddleware);
if (mode === 'development') {
app.use(secureHotMiddleware);
}
this.stopDevServer = () => {
return new Promise(resolve => {
secureMiddleware.close(resolve);
});
};
app.use(secureHotMiddleware);
}
// handle errors
// eslint-disable-next-line no-unused-vars
Expand Down
7 changes: 4 additions & 3 deletions packages/admin-ui/server/getWebpackConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const { enableDevFeatures, mode } = require('./env');

const isHerokuEnv = process.env.HEROKU === 'true';

module.exports = function({ adminMeta, entry }) {
module.exports = function({ adminMeta, entry, outputPath }) {
const templatePlugin = new HtmlWebpackPlugin({
title: 'KeystoneJS',
template: 'index.html',
Expand Down Expand Up @@ -87,8 +87,9 @@ module.exports = function({ adminMeta, entry }) {
entry:
mode === 'production' ? entryPath : [entryPath, 'webpack-hot-middleware/client?reload=true'],
output: {
filename: `${entry}.js`,
publicPath: adminMeta.adminPath,
path: outputPath,
filename: 'js/[name].[hash].bundle.js',
publicPath: adminMeta.adminPath + '/',
},
// TODO: We should pay attention to our bundle size at some point, but
// right now this is just noise
Expand Down
2 changes: 2 additions & 0 deletions packages/core/lib/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ module.exports = {
port = DEFAULT_PORT,
entryFile = DEFAULT_ENTRY,
serverConfig,
distDir,
_cwd = process.cwd(),
} = {}) =>
new Promise((resolve, reject) => {
Expand Down Expand Up @@ -73,6 +74,7 @@ module.exports = {
// Force the admin & port
...(appEntry.admin ? { adminUI: appEntry.admin } : {}),
port,
distDir: distDir || appEntry.distDir || 'dist',
emmatown marked this conversation as resolved.
Show resolved Hide resolved
});

return resolve({ server, keystone: appEntry.keystone });
Expand Down
Loading