Skip to content

Commit

Permalink
feat: css support
Browse files Browse the repository at this point in the history
  • Loading branch information
liximomo committed Feb 20, 2020
1 parent 1a0d92b commit efa2d9a
Show file tree
Hide file tree
Showing 25 changed files with 1,174 additions and 232 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"start": "cd packages/shuvi && node dist/cli.js start"
},
"devDependencies": {
"@types/send": "^0.14.5",
"jest": "^24.9.0",
"lerna": "^3.18.4",
"npm-run-all": "^4.1.5",
Expand Down
24 changes: 24 additions & 0 deletions packages/shared/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "@shuvi/shared",
"version": "0.0.1-alpha.0",
"repository": {
"type": "git",
"url": "git+https://github.com/liximomo/shuvi.git",
"directory": "packages/shared"
},
"author": "liximomo",
"license": "MIT",
"files": [
"lib"
],
"scripts": {
"dev": "tsc -p tsconfig.build.json -w"
},
"engines": {
"node": ">= 8.0.0"
},
"dependencies": {
"async-sema": "3.0.0",
"watchpack": "2.0.0-beta.12"
}
}
5 changes: 5 additions & 0 deletions packages/shared/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const NAME = "shuvi";

export const DEV_STYLE_ANCHOR_ID = "__style";

export const ROUTE_REGEXP = /^page-[0-9A-Fa-f]{4,4}$/;
8 changes: 8 additions & 0 deletions packages/shared/tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.build.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "lib"
},
"include": ["src"]
}
4 changes: 4 additions & 0 deletions packages/shared/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "../../tsconfig.json",
"include": ["src"]
}
2 changes: 2 additions & 0 deletions packages/shuvi/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"dependencies": {
"@shuvi/core": "^0.0.1-alpha.6",
"@shuvi/runtime-react": "^0.0.1-alpha.5",
"@shuvi/shared": "^0.0.1-alpha.0",
"@shuvi/toolpack": "^0.0.1-alpha.5",
"@shuvi/types": "^0.0.1-alpha.4",
"@shuvi/utils": "^0.0.1-alpha.4",
Expand All @@ -31,6 +32,7 @@
"express": "4.17.1",
"fs-extra": "8.1.0",
"path-to-regexp": "^6.1.0",
"send": "0.17.1",
"tiny-invariant": "1.1.0",
"webpack": "4.41.2",
"webpack-dev-middleware": "3.7.2",
Expand Down
23 changes: 17 additions & 6 deletions packages/shuvi/src/client/index.dev.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
/// <reference lib="dom" />
import { bootstrap } from "@shuvi-app/bootstrap";
import { DEV_STYLE_ANCHOR_ID } from "@shuvi/shared/lib/constants";
import { CLIENT_CONTAINER_ID } from "../shared/constants";
import initWebpackHMR from "./dev/webpackHotDevClient";
import { getAppData } from "./helpers/getAppData";

// type Comp = any;
// const shuvi: ShuviGlobal = (window as any)[CLIENT_GLOBAL_NAME];
const styleReady = new Promise(resolve => {
(window.requestAnimationFrame || setTimeout)(() => {
const el = document.querySelector(`#${DEV_STYLE_ANCHOR_ID}`)
?.previousElementSibling;
if (el) {
el.parentNode!.removeChild(el);
}
resolve();
});
});

initWebpackHMR();
bootstrap({
appData: getAppData(),
appContainer: document.getElementById(CLIENT_CONTAINER_ID)!
styleReady!.then(() => {
initWebpackHMR();
bootstrap({
appData: getAppData(),
appContainer: document.getElementById(CLIENT_CONTAINER_ID)!
});
});
14 changes: 14 additions & 0 deletions packages/shuvi/src/cmds/serve.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import program from "commander";
import path from "path";
import http from "http";
import { parse } from "url";
import { loadConfig } from "../helpers/loadConfig";
import Service from "../Service";
import { serveStatic } from "../helpers/serveStatic";
//@ts-ignore
import pkgInfo from "../../package.json";

Expand Down Expand Up @@ -33,6 +36,17 @@ async function main() {
const config = await loadConfig();
const service = new Service({ config });
const server = http.createServer((req, res) => {
const parsedUrl = parse(req.url!, true);
const pathname = parsedUrl.pathname || "/";
if (pathname.startsWith("/static")) {
serveStatic(
req,
res,
path.join(config.outputPath, "client", encodeURIComponent(pathname))
);
return;
}

service.renderPage(req, res);
});
server.listen(program.port, program.host, () => {
Expand Down
21 changes: 21 additions & 0 deletions packages/shuvi/src/helpers/serveStatic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { IncomingMessage, ServerResponse } from 'http'
import send from 'send'

export function serveStatic(
req: IncomingMessage,
res: ServerResponse,
path: string
): Promise<void> {
return new Promise((resolve, reject) => {
send(req, path)
.on('directory', () => {
// We don't allow directories to be read.
const err: any = new Error('No directory access')
err.code = 'ENOENT'
reject(err)
})
.on('error', reject)
.pipe(res)
.on('finish', resolve)
})
}
116 changes: 75 additions & 41 deletions packages/shuvi/src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { IncomingMessage, ServerResponse } from "http";
import { createApp } from "@shuvi/core";
import { AppCore, AppConfig, RouteComponent } from "@shuvi/types/core";
import * as Runtime from "@shuvi/types/Runtime";
import { ModuleManifest } from "@shuvi/types/build";
import { getProjectInfo } from "@shuvi/toolpack/lib/utils/typeScript";
import { DEV_STYLE_ANCHOR_ID } from "@shuvi/shared/lib/constants";
import Express from "express";
import { htmlEscapeJsonString } from "./helpers/htmlescape";
import DevServer from "./dev/devServer";
Expand All @@ -22,10 +22,12 @@ import { getWebpackManager } from "./webpack/webpackManager";
import { ModuleLoader } from "./webpack/output";
import { OnDemandRouteManager } from "./onDemandRouteManager";
import { runtime } from "./runtime";
import { acceptsHtml, dedupe } from "./utils";
import { acceptsHtml } from "./utils";

import AppData = Runtime.AppData;

const isDev = process.env.NODE_ENV === "development";

export default class Service {
private _app: AppCore;
private _webpackDistModuleLoader: ModuleLoader;
Expand Down Expand Up @@ -149,10 +151,7 @@ export default class Service {
next();
}

async renderPage(
req: IncomingMessage,
res: ServerResponse
): Promise<void> {
async renderPage(req: IncomingMessage, res: ServerResponse): Promise<void> {
const parsedUrl = parseUrl(req.url!, true);
const context: {
loadableModules: string[];
Expand Down Expand Up @@ -197,12 +196,14 @@ export default class Service {
routeProps
});
const dynamicImportIdSet = new Set<string>();
const dynamicImports: ModuleManifest[] = [];
const dynamicImportChunkSet = new Set<string>();
for (const mod of context.loadableModules) {
const manifestItem = loadableManifest[mod];
if (manifestItem) {
manifestItem.forEach(item => {
dynamicImports.push(item);
manifestItem.files.forEach(file => {
dynamicImportChunkSet.add(file);
});
manifestItem.children.forEach(item => {
dynamicImportIdSet.add(item.id as string);
});
}
Expand All @@ -211,8 +212,8 @@ export default class Service {
const documentProps = this._getDocumentProps({
appHtml,
routeProps,
dynamicImports,
dynamicImportIds: [...dynamicImportIdSet]
dynamicImportIds: [...dynamicImportIdSet],
dynamicImportAssets: [...dynamicImportChunkSet]
});
const Document = this._webpackDistModuleLoader.requireDocument();
const html = await runtime.renderDocument(Document.default || Document, {
Expand All @@ -224,62 +225,95 @@ export default class Service {

private _getDocumentProps({
appHtml,
dynamicImports,
routeProps,
dynamicImportIds,
routeProps
dynamicImportAssets
}: {
appHtml: string;
routeProps: Runtime.RouteProps;
dynamicImports: ModuleManifest[];
dynamicImportIds: Array<string | number>;
dynamicImportAssets: string[];
}): Runtime.DocumentProps {
const styles: Runtime.HtmlTag<"link">[] = [];
const scripts: Runtime.HtmlTag<"script">[] = [];
const mainStyles: Runtime.HtmlTag<"link" | "style">[] = [];
const mainScripts: Runtime.HtmlTag<"script">[] = [];
const entrypoints = this._webpackDistModuleLoader.getEntryAssets(
BUILD_CLIENT_RUNTIME_MAIN
);
entrypoints.forEach((asset: string) => {
if (/\.js$/.test(asset)) {
scripts.push({
tagName: "script",
entrypoints.js.forEach((asset: string) => {
mainScripts.push({
tagName: "script",
attrs: {
src: this._app.getPublicUrlPath(asset)
}
});
});
if (entrypoints.css) {
entrypoints.css.forEach((asset: string) => {
mainStyles.push({
tagName: "link",
attrs: {
rel: "stylesheet",
href: this._app.getPublicUrlPath(asset)
}
});
});
}

const preloadDynamicChunks: Runtime.HtmlTag<"link">[] = [];

for (const file of dynamicImportAssets) {
if (/\.js$/.test(file)) {
preloadDynamicChunks.push({
tagName: "link",
attrs: {
src: this._app.getPublicUrlPath(asset)
rel: "preload",
href: this._app.getPublicUrlPath(file),
as: "script"
}
});
} else if (/\.css$/.test(asset)) {
styles.push({
} else if (/\.css$/.test(file)) {
mainStyles.push({
tagName: "link",
attrs: {
rel: "stylesheet",
href: this._app.getPublicUrlPath(asset)
href: this._app.getPublicUrlPath(file)
}
});
}
});

const preloadDynamicChunks: Runtime.HtmlTag<"link">[] = dedupe(
dynamicImports,
"file"
).map((bundle: any) => {
return {
tagName: "link",
attrs: {
rel: "preload",
href: this._app.getPublicUrlPath(bundle.file),
as: "script"
}
};
});
}

const inlineAppData = this._getDocumentInlineAppData({
routeProps,
dynamicIds: dynamicImportIds
});

const headTags = [...preloadDynamicChunks, ...mainStyles];
if (isDev) {
headTags.push(
{
tagName: "style",
attrs: {
innerHtml: "body{display:none}"
}
},
/**
* this element is used to mount development styles so the
* ordering matches production
* (by default, style-loader injects at the bottom of <head />)
*/
{
tagName: "style",
attrs: {
id: DEV_STYLE_ANCHOR_ID
}
}
);
}

return {
headTags: [...styles, ...preloadDynamicChunks],
headTags,
contentTags: [this._getDocumentContent(appHtml)],
scriptTags: [inlineAppData, ...scripts]
scriptTags: [inlineAppData, ...mainScripts]
};
}

Expand Down
2 changes: 1 addition & 1 deletion packages/shuvi/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function acceptsHtml(
export function dedupe<T extends Record<string, any>, K extends keyof T>(
bundles: T[],
prop: K
): any[] {
): T[] {
const files = new Set();
const kept = [];

Expand Down
Loading

0 comments on commit efa2d9a

Please sign in to comment.