├── .browserslistrc
├── .huskyrc
├── public
├── favicon.ico
├── index.ssr.html
└── index.html
├── src
├── assets
│ └── logo.png
├── views
│ ├── About.vue
│ └── Home.vue
├── entry-client.js
├── store
│ └── index.js
├── app.js
├── router
│ └── index.js
├── App.vue
├── entry-server.js
└── components
│ └── HelloWorld.vue
├── babel.config.js
├── .editorconfig
├── .gitignore
├── .eslintrc.js
├── server
├── index.js
├── server.js
└── dev.server.js
├── README.zh-cn.md
├── README.md
├── package.json
└── vue.config.js
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not dead
4 |
--------------------------------------------------------------------------------
/.huskyrc:
--------------------------------------------------------------------------------
1 | {
2 | "hooks": {
3 | "pre-commit": "lint-staged"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codeDebugTest/vue2-ssr-demo/master/public/favicon.ico
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codeDebugTest/vue2-ssr-demo/master/src/assets/logo.png
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/src/views/About.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
This is an about page
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{js,jsx,ts,tsx,vue}]
2 | indent_style = space
3 | indent_size = 2
4 | trim_trailing_whitespace = true
5 | insert_final_newline = true
6 |
--------------------------------------------------------------------------------
/src/entry-client.js:
--------------------------------------------------------------------------------
1 | import { createApp } from './app'
2 |
3 | const { app, store } = createApp()
4 |
5 | if (window.__INITIAL_STATE__) {
6 | store.replaceState(window.__INITIAL_STATE__)
7 | }
8 |
9 | app.$mount('#app')
10 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 |
4 | Vue.use(Vuex)
5 |
6 | export function createStore () {
7 | return new Vuex.Store({
8 | state: {},
9 | mutations: {},
10 | actions: {}
11 | })
12 | }
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | pnpm-debug.log*
15 |
16 | # Editor directories and files
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw?
24 |
--------------------------------------------------------------------------------
/public/index.ssr.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | vue ssr demo
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/app.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 | import { createStore } from './store'
4 | import { createRouter } from './router'
5 |
6 | export function createApp () {
7 | const store = createStore()
8 | const router = createRouter()
9 |
10 | const app = new Vue({
11 | router,
12 | store,
13 | render: h => h(App)
14 | })
15 |
16 | return { app, router, store }
17 | };
18 |
--------------------------------------------------------------------------------
/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
5 |
6 |
7 |
8 |
19 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true
5 | },
6 | extends: [
7 | 'plugin:vue/essential',
8 | '@vue/standard'
9 | ],
10 | parserOptions: {
11 | parser: 'babel-eslint'
12 | },
13 | rules: {
14 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
15 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | vue ssr demo
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueRouter from 'vue-router'
3 |
4 | Vue.use(VueRouter)
5 |
6 | const routes = [
7 | {
8 | path: '/',
9 | name: 'home',
10 | component: () => import(/* webpackChunkName: "home" */ '../views/Home.vue')
11 | },
12 | {
13 | path: '/about',
14 | name: 'about',
15 | component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
16 | }
17 | ]
18 |
19 | export function createRouter () {
20 | return new VueRouter({
21 | mode: 'history',
22 | base: process.env.BASE_URL,
23 | routes
24 | })
25 | }
26 |
--------------------------------------------------------------------------------
/server/index.js:
--------------------------------------------------------------------------------
1 | const Koa = require('koa')
2 | const koaStatic = require('koa-static')
3 | const path = require('path')
4 |
5 | const resolve = file => path.resolve(__dirname, file)
6 | const app = new Koa()
7 |
8 | const isDev = process.env.NODE_ENV !== 'production'
9 | const router = isDev ? require('./dev.server') : require('./server')
10 |
11 | app.use(router.routes()).use(router.allowedMethods())
12 | // 开放目录
13 | app.use(koaStatic(resolve('../dist')))
14 |
15 | const port = process.env.PORT || 3000
16 | app.listen(port, () => {
17 | console.log(`server started at http://localhost:${port}`)
18 | })
19 |
20 | module.exports = app
21 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Home |
5 | About
6 |
7 |
8 |
9 |
10 |
11 |
33 |
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const path = require('path')
3 | const Router = require('koa-router')
4 | const router = new Router()
5 |
6 | const resolve = file => path.resolve(__dirname, file)
7 |
8 | const { createBundleRenderer } = require('vue-server-renderer')
9 | const bundle = require('../dist/vue-ssr-server-bundle.json')
10 | const clientManifest = require('../dist/vue-ssr-client-manifest.json')
11 |
12 | const renderer = createBundleRenderer(bundle, {
13 | runInNewContext: false,
14 | template: fs.readFileSync(resolve('../dist/index.ssr.html'), 'utf-8'),
15 | clientManifest
16 | })
17 |
18 | function renderToString (context) {
19 | return new Promise((resolve, reject) => {
20 | renderer.renderToString(context, (err, html) => {
21 | if (err) {
22 | return reject(err)
23 | }
24 | resolve(html)
25 | })
26 | })
27 | }
28 |
29 | const handleRequest = async (ctx, next) => {
30 | const url = ctx.path
31 | console.log('request path: ', url)
32 |
33 | ctx.res.setHeader('Content-Type', 'text/html')
34 | const context = { url }
35 | // render context data to HTML
36 | const html = await renderToString(context)
37 | ctx.body = html
38 | }
39 |
40 | // static resources (js|css|img|index.html|favicon.ico) need't server render
41 | router.get(/^\/(?!((js|css|img)\/)|(index\.html)|(favicon\.ico))/, handleRequest)
42 | module.exports = router
43 |
--------------------------------------------------------------------------------
/README.zh-cn.md:
--------------------------------------------------------------------------------
1 | # hello-world
2 |
3 | ### 动机
4 | 鉴于vue-ssr 官网例子 https://github.com/vuejs/vue-hackernews-2.0/
5 | 1. too old
6 | 2. too slow,通过webpack 打包,非vue-cli
7 | 3. unused github 上好多通过vue-cli的项目跑不起来
8 | 4. not compatible 不兼容 CSR模式
9 |
10 | 通过此项目你可以快速开始一个更具现代化地 vue-ssr 项目。
11 |
12 | #### 好处
13 | 1. 支持vue-cli4
14 | 2. 向下兼容:支持CSR(客户端渲染)模式
15 |
16 |
17 | ### 安装
18 | ```sh
19 | npm install
20 | ```
21 |
22 | ### 本地开发
23 |
24 | #### development ssr 模式
25 | ```sh
26 | npm run serve
27 | ```
28 | npm run serve 会启动两个服务:
29 | 1. npm run dev:client 静态资源服务(默认8090端口),用于供服务端渲染时hot-update 静态资源
30 | 2. npm run dev:server 服务端渲染(3000端口),真正的访问入口,该服务会将静态资源的访问代理到上面的静态资源服务
31 |
32 | 启动后,访问 http://localhost:3000 。dev模式带有hot-reload 的 服务端渲染。
33 |
34 | 其他:
35 | 1. 设置静态服务端口,通过设置环境变量 STATIC_PORT=xxxx 进行控制。
36 | 2. 若访问 http://localhost:8090 则走本地CSR模式,此时浏览器拿到的结果均为不经过SSR产出的结果
37 |
38 | #### development csr 模式
39 | ```sh
40 | npm run dev:client
41 | ```
42 | 本地开发,但不启用SSR服务端渲染
43 |
44 | #### production ssr 模式
45 | ```sh
46 | npm run build
47 | npm run serve
48 | ```
49 | 启动后,访问 http://localhost:3000 。线上ssr Server 模拟
50 |
51 | ### 构建
52 |
53 | ```sh
54 | npm run build
55 | ```
56 | 1. npm run build:client 编译生成ssr 生成客户端渲染所需内容
57 | 2. npm run build:server 编译生成ssr 生成服务端渲染所需内容
58 |
59 | ### 代码检查
60 |
61 | #### 仅检查
62 |
63 | ```sh
64 | npm run lint
65 | ```
66 |
67 | #### 检查并修复
68 |
69 | ```sh
70 | npm run lint:fix
71 | ```
72 |
73 | ## [搭建指南](https://github.com/codeDebugTest/blog/issues/4)
74 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # hello-world
2 | [中文文档](https://github.com/codeDebugTest/vue2-ssr-demo/blob/master/README.zh-cn.md)
3 |
4 | ## Project setup
5 | ```
6 | npm install
7 | ```
8 |
9 | ### Compiles and hot-reloads for development
10 | ```
11 | npm run serve
12 | ```
13 | **then visit http://localhost:3000 for ssr development**
14 |
15 | Take care:
16 | *npm run serve* will start two services:
17 | 1. static service running by *npm run dev:client* command. It use to hot-reloads static resources
18 | 2. SSR service running by *npm run dev:server* command. this
19 |
20 | others:
21 | 1. You can set static service port by setting env STATIC_PORT=xxxx on package.json's scripts。
22 | 2. If directly visit http://localhost:8090,then into the CSR mode. The html is not produced by SSR.
23 |
24 | #### CSR mode for development
25 | ```sh
26 | npm run dev:client
27 | ```
28 |
29 | #### SSR local server for production
30 | ```sh
31 | npm run build
32 | npm run serve
33 | ```
34 |
35 | ### Compiles and minifies for production
36 | ```
37 | npm run build
38 | ```
39 | *npm run build* command will execute two commands as shown below.
40 | 1. **npm run build:client** generate files for client render
41 | 2. **npm run build:server** generate files for server render
42 |
43 | ### Code lints
44 |
45 | #### only lints
46 |
47 | ```sh
48 | npm run lint
49 | ```
50 |
51 | #### Lints and fixes
52 | ```
53 | npm run lint:fix
54 | ```
55 |
56 | ### Customize configuration
57 | See [Configuration Reference](https://cli.vuejs.org/config/).
58 |
--------------------------------------------------------------------------------
/src/entry-server.js:
--------------------------------------------------------------------------------
1 | import { createApp } from './app'
2 |
3 | const isDev = process.env.NODE_ENV !== 'production'
4 |
5 | // This exported function will be called by `bundleRenderer`.
6 | // This is where we perform data-prefetching to determine the
7 | // state of our application before actually rendering it.
8 | // Since data fetching is async, this function is expected to
9 | // return a Promise that resolves to the app instance.
10 | export default context => {
11 | return new Promise((resolve, reject) => {
12 | const s = isDev && Date.now()
13 | const { app, router, store } = createApp()
14 |
15 | const { url } = context
16 | const { fullPath } = router.resolve(url).route
17 |
18 | if (fullPath !== url) {
19 | return reject(new Error(`can't find the path: ${url}`))
20 | }
21 |
22 | // set router's location
23 | router.push(url)
24 |
25 | // wait until router has resolved possible async hooks
26 | router.onReady(() => {
27 | const matchedComponents = router.getMatchedComponents()
28 | // no matched routes
29 | if (!matchedComponents.length) {
30 | return reject(new Error(`can't find the matched router for path: ${url}`))
31 | }
32 | // Call fetchData hooks on components matched by the route.
33 | // A preFetch hook dispatches a store action and returns a Promise,
34 | // which is resolved when the action is complete and store state has been
35 | // updated.
36 | Promise.all(matchedComponents.map(({ asyncData }) => asyncData && asyncData({
37 | store,
38 | route: router.currentRoute
39 | }))).then(() => {
40 | isDev && console.log(`data pre-fetch: ${Date.now() - s}ms`)
41 | // After all preFetch hooks are resolved, our store is now
42 | // filled with the state needed to render the app.
43 | // Expose the state on the render context, and let the request handler
44 | // inline the state in the HTML response. This allows the client-side
45 | // store to pick-up the server-side state without having to duplicate
46 | // the initial data fetching on the client.
47 | context.state = store.state
48 | resolve(app)
49 | }).catch(reject)
50 | }, reject)
51 | })
52 | }
53 |
--------------------------------------------------------------------------------
/src/components/HelloWorld.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ msg }}
4 |
5 | For a guide and recipes on how to configure / customize this project,
6 | check out the
7 | vue-cli documentation.
8 |
9 |
Installed CLI Plugins
10 |
16 |
Essential Links
17 |
24 |
Ecosystem
25 |
32 |
33 |
34 |
35 |
43 |
44 |
45 |
61 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue2-ssr-demo",
3 | "version": "1.0.0",
4 | "description": "a ssr demo project which build by vue-cli",
5 | "scripts": {
6 | "start": "cross-env NODE_ENV=production node ./server/index.js",
7 | "serve": "concurrently \"npm run dev:client\" \"npm run dev:server\"",
8 | "dev:client": "STATIC_PORT=8090 vue-cli-service serve",
9 | "dev:server": "STATIC_PORT=8090 cross-env WEBPACK_TARGET=server node ./server/index.js",
10 | "build": "rm -rf dist && npm run build:server && mv dist/vue-ssr-server-bundle.json bundle && npm run build:client && mv bundle dist/vue-ssr-server-bundle.json",
11 | "build:client": "NODE_ENV=production WEBPACK_TARGET=client vue-cli-service build",
12 | "build:server": "cross-env NODE_ENV=production WEBPACK_TARGET=server vue-cli-service build",
13 | "lint": "vue-cli-service lint --no-fix; stylelint src/**/*.{vue,css,less}",
14 | "lint:fix": "vue-cli-service lint; stylelint --fix src/**/*.{vue,css,less}"
15 | },
16 | "repository": {
17 | "type": "git",
18 | "url": "git+https://github.com/codeDebugTest/vue2-ssr-demo.git"
19 | },
20 | "keywords": [
21 | "vue",
22 | "vue-ssr",
23 | "vue-cli"
24 | ],
25 | "dependencies": {
26 | "axios": "^0.25.0",
27 | "core-js": "^3.8.3",
28 | "cross-env": "^7.0.3",
29 | "koa": "^2.13.4",
30 | "koa-router": "^9.4.0",
31 | "koa-static": "^5.0.0",
32 | "lodash": "^4.17.21",
33 | "vue": "^2.6.14",
34 | "vue-awesome": "^4.5.0",
35 | "vue-router": "^3.2.0",
36 | "vuex": "^3.4.0"
37 | },
38 | "devDependencies": {
39 | "@babel/eslint-parser": "^7.17.0",
40 | "@babel/eslint-plugin": "^7.16.5",
41 | "@vue/cli-plugin-babel": "~4.5.0",
42 | "@vue/cli-plugin-eslint": "~4.5.0",
43 | "@vue/cli-plugin-router": "~4.5.0",
44 | "@vue/cli-plugin-vuex": "~4.5.0",
45 | "@vue/cli-service": "~4.5.0",
46 | "@vue/eslint-config-standard": "^5.1.2",
47 | "babel-eslint": "^10.1.0",
48 | "concurrently": "^7.0.0",
49 | "eslint": "^7.32.0",
50 | "eslint-plugin-import": "^2.20.2",
51 | "eslint-plugin-node": "^11.1.0",
52 | "eslint-plugin-promise": "^4.2.1",
53 | "eslint-plugin-standard": "^4.0.0",
54 | "eslint-plugin-vue": "^7.15.1",
55 | "husky": "^4.3.8",
56 | "less": "^3.0.4",
57 | "less-loader": "^5.0.0",
58 | "lint-staged": "^9.5.0",
59 | "memory-fs": "^0.5.0",
60 | "stylelint": "^13.13.1",
61 | "vue-server-renderer": "^2.6.14",
62 | "vue-template-compiler": "^2.6.14",
63 | "webpack-node-externals": "^1.7.2",
64 | "webpackbar": "^5.0.2"
65 | },
66 | "gitHooks": {
67 | "pre-commit": "lint-staged"
68 | },
69 | "lint-staged": {
70 | "*.{js,jsx,vue}": [
71 | "vue-cli-service lint",
72 | "git add"
73 | ]
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/server/dev.server.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack')
2 | const axios = require('axios')
3 | const MemoryFS = require('memory-fs')
4 | const fs = require('fs')
5 | const path = require('path')
6 | const Router = require('koa-router')
7 |
8 | const webpackConfig = require('@vue/cli-service/webpack.config')
9 | const serverCompiler = webpack(webpackConfig)
10 | const mfs = new MemoryFS()
11 | serverCompiler.outputFileSystem = mfs
12 |
13 | let bundle
14 | serverCompiler.watch({}, (err, stats) => {
15 | if (err) {
16 | throw err
17 | }
18 |
19 | stats = stats.toJson()
20 | stats.errors.forEach(error => console.error(error))
21 | stats.warnings.forEach(warn => console.warn(warn))
22 | const bundlePath = path.join(
23 | webpackConfig.output.path,
24 | 'vue-ssr-server-bundle.json'
25 | )
26 | bundle = JSON.parse(mfs.readFileSync(bundlePath, 'utf-8'))
27 | console.log('new bundle generated')
28 | })
29 |
30 | const { createBundleRenderer } = require('vue-server-renderer')
31 | function renderToString (context, renderer) {
32 | return new Promise((resolve, reject) => {
33 | renderer.renderToString(context, (err, html) => {
34 | err ? reject(err) : resolve(html)
35 | })
36 | })
37 | }
38 | async function handleSsrRequest (ctx) {
39 | console.log('request path: ', ctx.path)
40 | if (!bundle) {
41 | ctx.body = 'you can visit the site until webpack compile completed'
42 | return
43 | }
44 |
45 | const clientManifestResp = await axios.get(`http://localhost:${process.env.STATIC_PORT}/vue-ssr-client-manifest.json`)
46 | const clientManifest = clientManifestResp.data
47 |
48 | const renderer = createBundleRenderer(bundle, {
49 | runInNewContext: false,
50 | template: fs.readFileSync(path.resolve(__dirname, '../public/index.ssr.html'), 'utf-8'),
51 | clientManifest
52 | })
53 | const html = await renderToString(ctx, renderer)
54 | ctx.body = html
55 | }
56 |
57 | async function handleStaticResourceRequest (ctx) {
58 | console.log('get static resource: ', ctx.path)
59 | let resource
60 | try {
61 | resource = await axios.get(
62 | `http://localhost:${process.env.STATIC_PORT}/${ctx.path}`,
63 | { responseType: 'stream' }
64 | )
65 | } catch (e) {
66 | console.log('static resources not found', ctx.path)
67 | ctx.status = '404'
68 | return
69 | }
70 |
71 | ctx.body = resource.data
72 | }
73 |
74 | const router = new Router()
75 | // because it can't read dist statice files on development mode, so we can proxy request by statice server.
76 | router.get(/^\/((js|css|img|)\/(.*)|favicon\.ico)$/, handleStaticResourceRequest)
77 | // for hot-update resource request
78 | router.get(/.*\.hot-update\.(json|js)/, handleStaticResourceRequest)
79 |
80 | router.get(/.*/, handleSsrRequest)
81 | module.exports = router
82 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
2 | const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
3 | const nodeExternals = require('webpack-node-externals')
4 | const WebpackBar = require('webpackbar')
5 | const CssContextLoader = require.resolve('./build-loaders/css-context')
6 | const { merge } = require('lodash')
7 | const path = require('path')
8 |
9 | const resolve = file => path.resolve(__dirname, file)
10 | const target = process.env.WEBPACK_TARGET || 'client'
11 | const isServer = target === 'server'
12 | const isDev = process.env.NODE_ENV !== 'production'
13 |
14 | module.exports = {
15 | lintOnSave: false,
16 | css: {
17 | extract: !isDev,
18 | loaderOptions: {
19 | less: {
20 | lessOptions: {
21 | javascriptEnabled: true
22 | }
23 | }
24 | }
25 | },
26 | configureWebpack: {
27 | entry: `./src/entry-${target}.js`,
28 | // This allows webpack to handle dynamic imports in a Node-appropriate
29 | // fashion, and also tells `vue-loader` to emit server-oriented code when
30 | // compiling Vue components.
31 | target: isServer ? 'node' : 'web',
32 | // For bundle renderer source map support
33 | devtool: 'source-map',
34 | // This tells the server bundle to use Node-style exports
35 | output: { libraryTarget: isServer ? 'commonjs2' : undefined },
36 | externals: isServer
37 | ? nodeExternals({
38 | // do not externalize dependencies that need to be processed by webpack.
39 | // you can add more file types here e.g. raw *.vue files
40 | // you should also whitelist deps that modifies `global` (e.g. polyfills)
41 | whitelist: [/\.css$/]
42 | })
43 | : undefined,
44 | // Server-side bundle should have one single entry file. Avoid using CommonsChunkPlugin in the server config
45 | optimization: { splitChunks: isServer ? false : undefined },
46 | plugins: [isServer ? new VueSSRServerPlugin() : new VueSSRClientPlugin()]
47 | },
48 | chainWebpack: config => {
49 | config.module
50 | .rule('vue')
51 | .use('vue-loader')
52 | .tap(options => merge(options, { optimizeSSR: isServer }))
53 |
54 | // add client/server compile process bar
55 | config.plugin('loader')
56 | .use(WebpackBar, [{ name: target, color: isServer ? 'orange' : 'green' }])
57 |
58 | if (!isDev) {
59 | // copy index.ssr.html for server render
60 | config.plugin('copy').tap(args => {
61 | args[0].push({
62 | from: resolve('public/index.ssr.html'),
63 | to: resolve('dist/index.ssr.html'),
64 | toType: 'file'
65 | })
66 | return args
67 | })
68 | }
69 |
70 | if (isServer) {
71 | // server side unused hot-reload
72 | config.plugins.delete('hmr')
73 |
74 | if (!isDev) {
75 | // css-loader mini-css-extract-plugin(extract-css-loader),will generate browser sentence such as document.getElementsByTagName xxxxx。
76 | // this will result in error (document not defined), running on server side。
77 | // so delete mini-css-extract-plugin and replace with css-context-loader。
78 | const langs = ['css', 'less']
79 | const types = ['vue-modules', 'vue', 'normal-modules', 'normal']
80 |
81 | langs.forEach(lang => {
82 | types.forEach(type => {
83 | const rule = config.module.rule(lang).oneOf(type)
84 | rule.uses.delete('extract-css-loader')
85 | rule.use('css-context-loader').loader(CssContextLoader).before('css-loader')
86 | })
87 | })
88 | }
89 | }
90 | },
91 | devServer: { port: isDev && !isServer ? process.env.STATIC_PORT : undefined }
92 | }
93 |
--------------------------------------------------------------------------------