Open wuyuedefeng opened 4 years ago
$ yarn add webpack-node-externals lodash.merge -D $ yarn add koa koa-static vue-server-renderer
/src/main.js
import Vue from 'vue' import App from './App.vue' import './registerServiceWorker' // import router from './router' import { createRouter } from './router' // import store from './store' import { createStore } from './store' import api from './utils/api' import directives from './utils/directives' import filters from './utils/filters' import './assets/stylesheets/application.scss' import i18n from './i18n' console.log(i18n.t('hello')) Vue.config.productionTip = false Vue.use(api) Vue.use(directives) Vue.use(filters) // 导出一个工厂函数,用于创建新的 // 应用程序、router 和 store 实例 export function createApp () { const router = createRouter() const store = createStore() const app = new Vue({ router, store, i18n, render: h => h(App) }) // app.$mount('#app') return { app, router, store } }
/src/router/index.js
import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter) const routes = [ { path: '/', name: 'Home', component: () => import('../pages/home.vue') }, { // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. path: '/about', name: 'About', component: () => import(/* webpackChunkName: "about" */ '../pages/about.vue') } ] export function createRouter () { return new VueRouter({ mode: 'history', base: process.env.BASE_URL, routes }) }
/src/store/index.js
import Vue from 'vue' import Vuex from 'vuex' import createPersistedState from 'vuex-persistedstate' import user from './modules/user' import app from './modules/app' Vue.use(Vuex) export function createStore () { return new Vuex.Store({ // state 持久化,防止f5刷新,导致数据消失 plugins: [createPersistedState({ storage: window.localStorage, reducer (val) { return { // 只保存module user内部所有变量持久化 user: val.user } } })], state: {}, mutations: {}, actions: {}, modules: { app, user } }) }
/src/entry-server.js
import { createApp } from './main' /* eslint-disable */ export default context => { // 因为有可能会是异步路由钩子函数或组件,所以我们将返回一个 Promise, // 以便服务器能够等待所有的内容在渲染前, // 就已经准备就绪。 return new Promise((resolve, reject) => { const { app, router } = createApp() // 设置服务器端 router 的位置 router.push(context.url) // 等到 router 将可能的异步组件和钩子函数解析完 router.onReady(() => { const matchedComponents = router.getMatchedComponents() // 匹配不到的路由,执行 reject 函数,并返回 404 if (!matchedComponents.length) { return reject({ code: 404 }) } // Promise 应该 resolve 应用程序实例,以便它可以渲染 resolve(app) }, reject) }) }
/src/entry-client.js
import { createApp } from './main' // 客户端特定引导逻辑…… const { app } = createApp() // 这里假定 App.vue 模板中根元素具有 `id="app"` app.$mount('#app')
/public/index.temp.html
<html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> </head> <body> <!--vue-ssr-outlet--> </body> </html>
package.json
"build:client": "vue-cli-service build", "build:server": "WEBPACK_TARGET=node vue-cli-service build --mode server", "build:win": "npm run build:server && mv dist/vue-ssr-server-bundle.json bundle && npm run build:client && mv bundle dist/vue-ssr-server-bundle.json",
/server.js
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin') const VueSSRClientPlugin = require('vue-server-renderer/client-plugin') const nodeExternals = require('webpack-node-externals') const merge = require("lodash.merge") const TARGET_NODE = process.env.WEBPACK_TARGET === 'node' const target = TARGET_NODE ? 'server' : 'client' const path = require('path') function resolve (dir) { return path.join(__dirname, dir) } console.log(`\n --- target:${target} ---\n`) module.exports = { // transpileDependencies: [/zzp-ui/], productionSourceMap: false, // publicPath: process.env.NODE_ENV !== 'development' ? './' : '/', // outputDir: 'dist', devServer: { open: true, host: '0.0.0.0', port: '8080', disableHostCheck: true, // 解决127.0.0.1指向其他域名时出现"Invalid Host header"问题 proxy: { '/api': { target: '<url>', changOrigin: true // 配置跨域 // ws: true, // 配置ws跨域 // secure: false, // https协议才设置 } }, before: app => { require('./mock')(app) } }, // 自定义入口和模板 // pages: { // index: { // entry: 'src/main.js', // template: 'public/index.html', // filename: 'index.html' // } // }, chainWebpack: config => { config.resolve.alias.set('@pages', resolve('src/pages')) // eg: 更改编译忽略某些svg文件的加载 config.module.rule('svg').exclude.add(resolve('src/components/shared/SvgIcon')).end() config.module.rule('icons').test(/\.svg$/).include.add(resolve('src/components/shared/SvgIcon')).end().use('svg-sprite-loader').loader('svg-sprite-loader').options({ symbolId: 'icon-[name]' }).end() // 如果使用多页面打包,使用vue inspect --plugins查看html是否在结果数组中 config.plugin('html').tap(args => { args[0].title = '前后端分离' return args }) // ssr config.module.rule('vue').use('vue-loader').tap(options => { merge(options, { optimizeSSR: false }) }) }, configureWebpack: () => ({ // 将 entry 指向应用程序的 server / client 文件 entry: `./src/entry-${target}.js`, // 对 bundle renderer 提供 source map 支持 devtool: 'source-map', target: TARGET_NODE ? 'node' : 'web', node: TARGET_NODE ? undefined : false, output: { libraryTarget: TARGET_NODE ? 'commonjs2' : undefined }, // https://webpack.js.org/configuration/externals/#function // https://github.com/liady/webpack-node-externals // 外置化应用程序依赖模块。可以使服务器构建速度更快, // 并生成较小的 bundle 文件。 externals: TARGET_NODE ? nodeExternals({ // 不要外置化 webpack 需要处理的依赖模块。 // 你可以在这里添加更多的文件类型。例如,未处理 *.vue 原始文件, // 你还应该将修改 `global`(例如 polyfill)的依赖模块列入白名单 whitelist: [/\.css$/] }) : undefined, optimization: { splitChunks: TARGET_NODE ? false : undefined }, plugins: [TARGET_NODE ? new VueSSRServerPlugin() : new VueSSRClientPlugin()] }), // configureWebpack: config => { // config.module.rules.push({ // test: /.md$/, // use: [{ // loader: 'text-loader' // }] // }) // }, css: { loaderOptions: { // pass options to sass-loader sass: { // @/ is an alias to src/ // before key is 'data', now change to 'prependData' so this assumes you have a file named `src/variables.scss` prependData: '@import "@/assets/stylesheets/_variables.scss";' }, postcss: { plugins: [ require('postcss-pxtorem')({ // rootValue: 37.5, // 效果图375 rootValue: (input) => { // eslint-disable-next-line no-useless-escape if (/@media\sonly\sscreen\sand\s\(min\-width:\s1024px\)/i.test(input && input.css)) { return 144 } return 37.5 }, propList: ['*'], // 属性的选择器,*表示通用 selectorBlackList: ['.px-'] // 忽略的选择器 .ig- 表示 .ig- 开头的都不会转换 // exclude: /node_modules/i }) ], /* You can extend webpack config here */ extend (config, ctx) {} } } }, pluginOptions: { i18n: { locale: 'en', fallbackLocale: 'en', localeDir: 'locales', enableInSFC: true } } }
npm run build:win & node server 是服务端渲染启动部署流程 npm run server 是本地开发环境启动流程
npm run build:win
node server
npm run server
参考资料库
Learning requires teaching by hand to hand.
vue3 + vue-router + pinia + vite + koa + koa-router 项目代码: https://gitee.com/zezeping/zhuliba/tree/master/frontend/user-web
安装插件
改造文件
/src/main.js
/src/router/index.js
/src/store/index.js
创建新文件
/src/entry-server.js
/src/entry-client.js
/public/index.temp.html
package.json
/server.js
Webpack
npm run build:win
&node server
是服务端渲染启动部署流程npm run server
是本地开发环境启动流程配置代码: https://github.com/vue-quick-framework/vue-base-structrue/tree/feature/ssr
参考资料库