mirror of
https://github.com/kevin-DL/sapper.git
synced 2026-01-21 06:45:00 +00:00
start generating client-side bundle. WIP
This commit is contained in:
@@ -27,8 +27,6 @@ A Sapper app is just an Express app (conventionally, `server.js`) that uses the
|
|||||||
const app = require('express')();
|
const app = require('express')();
|
||||||
const sapper = require('sapper');
|
const sapper = require('sapper');
|
||||||
|
|
||||||
const app = express();
|
|
||||||
|
|
||||||
app.use(sapper());
|
app.use(sapper());
|
||||||
|
|
||||||
const { PORT = 3000 } = process.env;
|
const { PORT = 3000 } = process.env;
|
||||||
@@ -74,7 +72,7 @@ Or, if you omit the `res` argument, it can use the return value:
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
// routes/api/post/%id%.js
|
// routes/api/post/%id%.js
|
||||||
export async function get(req, res) {
|
export async function get(req) {
|
||||||
return await getPostFromDatabase(req.params.id);
|
return await getPostFromDatabase(req.params.id);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
41
connect.js
41
connect.js
@@ -1,14 +1,20 @@
|
|||||||
require('svelte/ssr/register');
|
require('svelte/ssr/register');
|
||||||
const esm = require('@std/esm');
|
const esm = require('@std/esm');
|
||||||
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const glob = require('glob');
|
const glob = require('glob');
|
||||||
|
const tmp = require('tmp');
|
||||||
const create_matchers = require('./utils/create_matchers.js');
|
const create_matchers = require('./utils/create_matchers.js');
|
||||||
|
const create_app = require('./utils/create_app.js');
|
||||||
|
const create_webpack_compiler = require('./utils/create_webpack_compiler.js');
|
||||||
|
|
||||||
require = esm(module, {
|
const esmRequire = esm(module, {
|
||||||
esm: 'all'
|
esm: 'all'
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = function connect(opts = {}) {
|
const dir = tmp.dirSync({ unsafeCleanup: true });
|
||||||
|
|
||||||
|
module.exports = function connect(opts) {
|
||||||
const routes = path.resolve('routes');
|
const routes = path.resolve('routes');
|
||||||
const out = path.resolve('.sapper');
|
const out = path.resolve('.sapper');
|
||||||
|
|
||||||
@@ -18,19 +24,40 @@ module.exports = function connect(opts = {}) {
|
|||||||
let server_routes = glob.sync('**/*.+(js|mjs)', { cwd: routes });
|
let server_routes = glob.sync('**/*.+(js|mjs)', { cwd: routes });
|
||||||
let server_route_matchers = create_matchers(server_routes);
|
let server_route_matchers = create_matchers(server_routes);
|
||||||
|
|
||||||
|
// create_app(routes, dir.name, page_matchers, opts.dev);
|
||||||
|
create_app(routes, out, page_matchers, opts.dev);
|
||||||
|
|
||||||
|
const webpack_compiler = create_webpack_compiler(
|
||||||
|
path.join(out, 'main.js'),
|
||||||
|
path.resolve('.sapper/webpack'),
|
||||||
|
opts.dev
|
||||||
|
);
|
||||||
|
|
||||||
return async function(req, res, next) {
|
return async function(req, res, next) {
|
||||||
const url = req.url.replace(/\?.+/, '');
|
const url = req.url.replace(/\?.+/, '');
|
||||||
|
|
||||||
|
if (url.startsWith('/webpack/')) {
|
||||||
|
fs.createReadStream(path.resolve('.sapper' + url)).pipe(res);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < page_matchers.length; i += 1) {
|
for (let i = 0; i < page_matchers.length; i += 1) {
|
||||||
const matcher = page_matchers[i];
|
const matcher = page_matchers[i];
|
||||||
if (matcher.test(url)) {
|
if (matcher.test(url)) {
|
||||||
const params = matcher.exec(url);
|
const params = matcher.exec(url);
|
||||||
const Component = require(`${routes}/${matcher.file}`);
|
const Component = require(`${routes}/${matcher.file}`);
|
||||||
|
|
||||||
res.end(Component.render({
|
const app = await webpack_compiler.app;
|
||||||
params,
|
|
||||||
query: req.query
|
const page = opts.template({
|
||||||
}));
|
app,
|
||||||
|
html: Component.render({
|
||||||
|
params,
|
||||||
|
query: req.query
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
res.end(page);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,7 +66,7 @@ module.exports = function connect(opts = {}) {
|
|||||||
const matcher = server_route_matchers[i];
|
const matcher = server_route_matchers[i];
|
||||||
if (matcher.test(url)) {
|
if (matcher.test(url)) {
|
||||||
req.params = matcher.exec(url);
|
req.params = matcher.exec(url);
|
||||||
const route = require(`${routes}/${matcher.file}`);
|
const route = esmRequire(`${routes}/${matcher.file}`);
|
||||||
|
|
||||||
const handler = route[req.method.toLowerCase()];
|
const handler = route[req.method.toLowerCase()];
|
||||||
if (handler) {
|
if (handler) {
|
||||||
|
|||||||
2738
package-lock.json
generated
2738
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -7,11 +7,14 @@
|
|||||||
"test": "test"
|
"test": "test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@std/esm": "^0.18.0"
|
"@std/esm": "^0.18.0",
|
||||||
|
"extract-text-webpack-plugin": "^3.0.2",
|
||||||
|
"tmp": "0.0.33",
|
||||||
|
"webpack": "^3.10.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"mocha": "^4.0.1",
|
"mocha": "^4.0.1",
|
||||||
"svelte": "^1.47.0"
|
"svelte": "^1.47.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "mocha --opts mocha.opts"
|
"test": "mocha --opts mocha.opts"
|
||||||
|
|||||||
38
templates/main.js
Normal file
38
templates/main.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
window.addEventListener('click', event => {
|
||||||
|
let a = event.target;
|
||||||
|
while (a && a.nodeName !== 'A') a = a.parentNode;
|
||||||
|
if (!a) return;
|
||||||
|
|
||||||
|
if (navigate(new URL(a.href))) event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
const target = document.querySelector('main');
|
||||||
|
let component;
|
||||||
|
|
||||||
|
function navigate(url) {
|
||||||
|
if (url.origin !== window.location.origin) return;
|
||||||
|
|
||||||
|
let match;
|
||||||
|
let params = {};
|
||||||
|
const query = {};
|
||||||
|
|
||||||
|
function render(mod) {
|
||||||
|
if (component) {
|
||||||
|
component.destroy();
|
||||||
|
} else {
|
||||||
|
target.innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
component = new mod.default({
|
||||||
|
target,
|
||||||
|
data: { query, params },
|
||||||
|
hydrate: !!component
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ROUTES
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
navigate(window.location);
|
||||||
27
utils/create_app.js
Normal file
27
utils/create_app.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const tmp = require('tmp');
|
||||||
|
|
||||||
|
const template = fs.readFileSync(path.resolve(__dirname, '../templates/main.js'), 'utf-8');
|
||||||
|
|
||||||
|
module.exports = function create_app(routes, dest, matchers, dev) {
|
||||||
|
// TODO in dev mode, watch files
|
||||||
|
|
||||||
|
const code = matchers
|
||||||
|
.map(matcher => {
|
||||||
|
const condition = matcher.dynamic.length === 0 ?
|
||||||
|
`url.pathname === '/${matcher.parts.join('/')}'` :
|
||||||
|
`${matcher.pattern}.test(url.pathname)`;
|
||||||
|
|
||||||
|
return `
|
||||||
|
if (${condition}) {
|
||||||
|
import('../routes/${matcher.file}').then(render);
|
||||||
|
}
|
||||||
|
`.replace(/^\t{3}/gm, '').trim();
|
||||||
|
})
|
||||||
|
.join(' else ');
|
||||||
|
|
||||||
|
const main = template.replace('// ROUTES', code);
|
||||||
|
|
||||||
|
fs.writeFileSync(path.join(dest, 'main.js'), main);
|
||||||
|
};
|
||||||
@@ -30,6 +30,7 @@ module.exports = function create_matchers(files) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
file,
|
file,
|
||||||
|
pattern,
|
||||||
test,
|
test,
|
||||||
exec,
|
exec,
|
||||||
parts,
|
parts,
|
||||||
@@ -41,12 +42,5 @@ module.exports = function create_matchers(files) {
|
|||||||
(a.dynamic.length - b.dynamic.length) || // match static paths first
|
(a.dynamic.length - b.dynamic.length) || // match static paths first
|
||||||
(b.parts.length - a.parts.length) // match longer paths first
|
(b.parts.length - a.parts.length) // match longer paths first
|
||||||
);
|
);
|
||||||
})
|
|
||||||
.map(matcher => {
|
|
||||||
return {
|
|
||||||
file: matcher.file,
|
|
||||||
test: matcher.test,
|
|
||||||
exec: matcher.exec
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
81
utils/create_webpack_compiler.js
Normal file
81
utils/create_webpack_compiler.js
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const webpack = require('webpack');
|
||||||
|
const ExtractTextPlugin = require("extract-text-webpack-plugin");
|
||||||
|
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
|
||||||
|
|
||||||
|
// TODO make the webpack config, err, configurable
|
||||||
|
|
||||||
|
module.exports = function create_webpack_compiler(main, dest, dev) {
|
||||||
|
const compiler = {};
|
||||||
|
|
||||||
|
const _ = webpack({
|
||||||
|
entry: {
|
||||||
|
main
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.js', '.html']
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
path: dest,
|
||||||
|
filename: '[name].[hash].js',
|
||||||
|
chunkFilename: '[name].[id].js',
|
||||||
|
publicPath: '/webpack/'
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.html$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
use: {
|
||||||
|
loader: 'svelte-loader',
|
||||||
|
options: {
|
||||||
|
emitCss: true,
|
||||||
|
cascade: false,
|
||||||
|
store: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.css$/,
|
||||||
|
use: ExtractTextPlugin.extract({
|
||||||
|
fallback: 'style-loader',
|
||||||
|
use: [{ loader: 'css-loader', options: { sourceMap: dev } }]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new ExtractTextPlugin('main.css'),
|
||||||
|
!dev && new webpack.optimize.ModuleConcatenationPlugin(),
|
||||||
|
!dev && new UglifyJSPlugin()
|
||||||
|
].filter(Boolean),
|
||||||
|
devtool: dev ? 'inline-source-map' : false
|
||||||
|
});
|
||||||
|
|
||||||
|
if (false) { // TODO watch in dev
|
||||||
|
// TODO how can we invalidate compiler.app when watcher restarts?
|
||||||
|
compiler.app = new Promise((fulfil, reject) => {
|
||||||
|
_.watch({}, (err, stats) => {
|
||||||
|
if (err || stats.hasErrors()) {
|
||||||
|
// TODO handle errors
|
||||||
|
}
|
||||||
|
|
||||||
|
const filename = stats.toJson().assetsByChunkName.main;
|
||||||
|
fulfil(`/webpack/${filename}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
compiler.app = new Promise((fulfil, reject) => {
|
||||||
|
_.run((err, stats) => {
|
||||||
|
if (err || stats.hasErrors()) {
|
||||||
|
// TODO handle errors
|
||||||
|
}
|
||||||
|
|
||||||
|
const filename = stats.toJson().assetsByChunkName.main;
|
||||||
|
fulfil(`/webpack/${filename}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return compiler;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user