mirror of
https://github.com/aelve/guide.git
synced 2024-12-22 04:11:32 +03:00
Fully replaced frontend part. Now everything written in typescript.
This commit is contained in:
parent
07067a0842
commit
b7ae82762c
@ -1,22 +1,21 @@
|
||||
{
|
||||
"presets": [
|
||||
["env", { "modules": false }],
|
||||
["stage-2"]
|
||||
["env", {
|
||||
"targets": {
|
||||
"browsers": ["last 3 versions", "> 2%", "ie >= 10", "Firefox >= 30", "Chrome >= 30"]
|
||||
},
|
||||
"modules": false,
|
||||
"loose": true,
|
||||
"useBuiltIns": true
|
||||
}],
|
||||
"stage-2"
|
||||
],
|
||||
"plugins": [
|
||||
"transform-runtime",
|
||||
["transform-imports", {
|
||||
"vuetify": {
|
||||
"transform": "vuetify/es5/components/${member}",
|
||||
"preventFullImport": true
|
||||
}
|
||||
}]
|
||||
"transform-runtime"
|
||||
],
|
||||
"comments": false,
|
||||
"env": {
|
||||
"test": {
|
||||
"presets": ["env", "stage-2"],
|
||||
"plugins": ["istanbul"]
|
||||
"plugins": ["transform-es2015-modules-commonjs"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,23 @@
|
||||
# http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# The JSON files contain newlines inconsistently
|
||||
[*.json]
|
||||
insert_final_newline = ignore
|
||||
|
||||
# Minified JavaScript files shouldn't be changed
|
||||
[**.min.js]
|
||||
indent_style = ignore
|
||||
insert_final_newline = ignore
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
|
5
front/.gitignore
vendored
5
front/.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
.DS_Store
|
||||
node_modules/
|
||||
.idea/
|
||||
dist/
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
|
||||
|
@ -1,19 +1,42 @@
|
||||
# Vuetify Webpack SSR Template
|
||||
# @vert/vue-ssr-template
|
||||
|
||||
> Vuetify SSR Webpack Template
|
||||
> Vue project template with SSR, Vert and TypeScript support.
|
||||
|
||||
## Build Setup
|
||||
A tiny demo to show how to use [@vert/core](https://github.com/LancerComet/Vert-Core).
|
||||
|
||||
``` bash
|
||||
vue init vuetifyjs/webpack-ssr
|
||||
You can use this as your vert template.
|
||||
|
||||
cd webpack-ssr
|
||||
## Features
|
||||
- Webpack 4.
|
||||
- Vue SSR support.
|
||||
- Full TypeScript.
|
||||
- Code in OOP.
|
||||
- [@vert/core](https://github.com/LancerComet/Vert-Core) support.
|
||||
|
||||
# npm
|
||||
npm install
|
||||
## Commands
|
||||
|
||||
# yarn
|
||||
yarn
|
||||
```
|
||||
- `client:dev` - Start developing environment of client content.
|
||||
- `client:build` - Build client content.
|
||||
- `server:dev` - Start developing environment of ssr server.
|
||||
- `server:build` - Build ssr content.
|
||||
- `build-all` - Run `client:build` and `server:build` at same time. Please run this command for deployment.
|
||||
- `start:dev` - Run `client:dev` and `server:dev` at same time. Please run this command for development.
|
||||
- `start:prod` - Start ssr server in production environment. You should run `build-all` first to make it works.
|
||||
|
||||
For additional information, please visit the [Official Documentation](https://vuetifyjs.com).
|
||||
## Development
|
||||
|
||||
Normally just run `start:dev` to start both normal http and ssr environment, and then:
|
||||
|
||||
- Go `localhost:4000` for normal http environment.
|
||||
- Go `localhost:5000` for SSR-enabled environment.
|
||||
|
||||
Ports setting is stored in `build/build-config.js`.
|
||||
|
||||
## Production
|
||||
|
||||
Run `build-all` to files for production.
|
||||
|
||||
Then,
|
||||
|
||||
- Send your static files which are located in `dist/` to your (CDN) server.
|
||||
- If you want to host a SSR server, the simplest way is to send the whole project to somewhere and run `start:prod`. But you can also build service by your own, just transfer vue-bundle-json files under `dist/` to your location and setup your API Gateway.
|
||||
|
@ -1,109 +0,0 @@
|
||||
<template>
|
||||
<v-app dark>
|
||||
<v-navigation-drawer
|
||||
:mini-variant="miniVariant"
|
||||
:clipped="clipped"
|
||||
v-model="drawer"
|
||||
fixed
|
||||
app
|
||||
>
|
||||
<v-list>
|
||||
<v-list-tile
|
||||
router
|
||||
:to="item.to"
|
||||
:key="i"
|
||||
v-for="(item, i) in items"
|
||||
exact
|
||||
>
|
||||
<v-list-tile-action>
|
||||
<v-icon v-html="item.icon"></v-icon>
|
||||
</v-list-tile-action>
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title v-text="item.title"></v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
</v-list>
|
||||
</v-navigation-drawer>
|
||||
<v-toolbar fixed>
|
||||
<v-toolbar-side-icon @click.stop="drawer = !drawer"></v-toolbar-side-icon>
|
||||
</v-toolbar>
|
||||
<v-toolbar fixed app :clipped-left="clipped">
|
||||
<v-toolbar-side-icon @click.stop="drawer = !drawer"></v-toolbar-side-icon>
|
||||
<v-btn
|
||||
icon
|
||||
@click.native.stop="miniVariant = !miniVariant"
|
||||
>
|
||||
<v-icon v-html="miniVariant ? 'chevron_right' : 'chevron_left'"></v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
icon
|
||||
@click.native.stop="clipped = !clipped"
|
||||
>
|
||||
<v-icon>web</v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
icon
|
||||
@click.native.stop="fixed = !fixed"
|
||||
>
|
||||
<v-icon>remove</v-icon>
|
||||
</v-btn>
|
||||
<v-toolbar-title v-text="title"></v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn
|
||||
icon
|
||||
@click.native.stop="rightDrawer = !rightDrawer"
|
||||
>
|
||||
<v-icon>menu</v-icon>
|
||||
</v-btn>
|
||||
</v-toolbar>
|
||||
<v-content>
|
||||
<v-container fluid>
|
||||
<v-slide-y-transition mode="out-in">
|
||||
<router-view></router-view>
|
||||
</v-slide-y-transition>
|
||||
</v-container>
|
||||
</v-content>
|
||||
<v-navigation-drawer
|
||||
temporary
|
||||
:right="right"
|
||||
v-model="rightDrawer"
|
||||
fixed
|
||||
>
|
||||
<v-list>
|
||||
<v-list-tile @click.native="right = !right">
|
||||
<v-list-tile-action>
|
||||
<v-icon light>compare_arrows</v-icon>
|
||||
</v-list-tile-action>
|
||||
<v-list-tile-title>Switch drawer (click me)</v-list-tile-title>
|
||||
</v-list-tile>
|
||||
</v-list>
|
||||
</v-navigation-drawer>
|
||||
<v-footer :fixed="fixed" app>
|
||||
<span>© 2017</span>
|
||||
</v-footer>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Meta from 'mixins/meta'
|
||||
|
||||
export default {
|
||||
mixins: [Meta],
|
||||
|
||||
data () {
|
||||
return {
|
||||
clipped: false,
|
||||
drawer: true,
|
||||
fixed: false,
|
||||
items: [
|
||||
{ icon: 'apps', title: 'Welcome', to: '/' },
|
||||
{ icon: 'bubble_chart', title: 'Inspire', to: '/inspire' }
|
||||
],
|
||||
miniVariant: false,
|
||||
right: true,
|
||||
rightDrawer: false,
|
||||
title: 'Vuetify.js'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -1,76 +0,0 @@
|
||||
import Vue from 'vue'
|
||||
import {
|
||||
Vuetify,
|
||||
VApp,
|
||||
VNavigationDrawer,
|
||||
VFooter,
|
||||
VList,
|
||||
VBtn,
|
||||
VIcon,
|
||||
VGrid,
|
||||
VToolbar,
|
||||
VCard,
|
||||
transitions
|
||||
} from 'vuetify'
|
||||
import '../node_modules/vuetify/src/stylus/app.styl'
|
||||
import App from './App.vue'
|
||||
import Components from 'components/_index'
|
||||
|
||||
import { createStore } from 'store/index'
|
||||
import { createRouter } from 'router/index'
|
||||
import { sync } from 'vuex-router-sync'
|
||||
|
||||
Vue.use(Vuetify, {
|
||||
components: {
|
||||
VApp,
|
||||
VNavigationDrawer,
|
||||
VFooter,
|
||||
VList,
|
||||
VBtn,
|
||||
VIcon,
|
||||
VGrid,
|
||||
VToolbar,
|
||||
VCard,
|
||||
transitions
|
||||
},
|
||||
theme: {
|
||||
primary: '#ee44aa',
|
||||
secondary: '#424242',
|
||||
accent: '#82B1FF',
|
||||
error: '#FF5252',
|
||||
info: '#2196F3',
|
||||
success: '#4CAF50',
|
||||
warning: '#FFC107'
|
||||
}
|
||||
})
|
||||
|
||||
Object.keys(Components).forEach(key => {
|
||||
Vue.component(key, Components[key])
|
||||
})
|
||||
|
||||
// Expose a factory function that creates a fresh set of store, router,
|
||||
// app instances on each call (which is called for each SSR request)
|
||||
export function createApp (ssrContext) {
|
||||
// create store and router instances
|
||||
const store = createStore()
|
||||
const router = createRouter()
|
||||
|
||||
// sync the router with the vuex store.
|
||||
// this registers `store.state.route`
|
||||
sync(store, router)
|
||||
|
||||
// create the app instance.
|
||||
// here we inject the router, store and ssr context to all child components,
|
||||
// making them available everywhere as `this.$router` and `this.$store`.
|
||||
const app = new Vue({
|
||||
router,
|
||||
store,
|
||||
ssrContext,
|
||||
render: h => h(App)
|
||||
})
|
||||
|
||||
// expose the app, the router and the store.
|
||||
// note we are not mounting the app here, since bootstrapping will be
|
||||
// different depending on whether we are in a browser or on the server.
|
||||
return { app, router, store }
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
import Vue from 'vue'
|
||||
import 'es6-promise/auto'
|
||||
import { createApp } from './app'
|
||||
|
||||
const { app, router, store } = createApp()
|
||||
|
||||
// prime the store with server-initialized state.
|
||||
// the state is determined during SSR and inlined in the page markup.
|
||||
if (window.__INITIAL_STATE__) {
|
||||
store.replaceState(window.__INITIAL_STATE__)
|
||||
}
|
||||
|
||||
// wait until router has resolved all async before hooks
|
||||
// and async components...
|
||||
router.onReady(() => {
|
||||
// Add router hook for handling asyncData.
|
||||
// Doing it after initial route is resolved so that we don't double-fetch
|
||||
// the data that we already have. Using router.beforeResolve() so that all
|
||||
// async components are resolved.
|
||||
router.beforeResolve((to, from, next) => {
|
||||
const matched = router.getMatchedComponents(to)
|
||||
const prevMatched = router.getMatchedComponents(from)
|
||||
let diffed = false
|
||||
const activated = matched.filter((c, i) => {
|
||||
return diffed || (diffed = (prevMatched[i] !== c))
|
||||
})
|
||||
if (!activated.length) {
|
||||
return next()
|
||||
}
|
||||
Promise.all(activated.map(c => {
|
||||
if (c.asyncData) {
|
||||
return c.asyncData({ store, route: to })
|
||||
}
|
||||
})).then(() => {
|
||||
next()
|
||||
}).catch(next)
|
||||
})
|
||||
|
||||
// actually mount to DOM
|
||||
app.$mount('#app')
|
||||
})
|
@ -1,47 +0,0 @@
|
||||
import { createApp } from './app'
|
||||
|
||||
const isDev = process.env.NODE_ENV !== 'production'
|
||||
|
||||
// This exported function will be called by `bundleRenderer`.
|
||||
// This is where we perform data-prefetching to determine the
|
||||
// state of our application before actually rendering it.
|
||||
// Since data fetching is async, this function is expected to
|
||||
// return a Promise that resolves to the app instance.
|
||||
export default context => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const s = isDev && Date.now()
|
||||
const { app, router, store } = createApp(context)
|
||||
|
||||
// set router's location
|
||||
router.push(context.url)
|
||||
|
||||
// wait until router has resolved possible async hooks
|
||||
router.onReady(() => {
|
||||
const matchedComponents = router.getMatchedComponents()
|
||||
// no matched routes
|
||||
if (!matchedComponents.length) {
|
||||
reject({ code: 404 })
|
||||
}
|
||||
// Call fetchData hooks on components matched by the route.
|
||||
// A preFetch hook dispatches a store action and returns a Promise,
|
||||
// which is resolved when the action is complete and store state has been
|
||||
// updated.
|
||||
Promise.all(matchedComponents.map(component => {
|
||||
return component.asyncData && component.asyncData({
|
||||
store,
|
||||
route: router.currentRoute
|
||||
})
|
||||
})).then(() => {
|
||||
isDev && console.log(`data pre-fetch: ${Date.now() - s}ms`)
|
||||
// After all preFetch hooks are resolved, our store is now
|
||||
// filled with the state needed to render the app.
|
||||
// Expose the state on the render context, and let the request handler
|
||||
// inline the state in the HTML response. This allows the client-side
|
||||
// store to pick-up the server-side state without having to duplicate
|
||||
// the initial data fetching on the client.
|
||||
context.state = store.state
|
||||
resolve(app)
|
||||
}).catch(reject)
|
||||
}, reject)
|
||||
})
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title></title>
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
|
||||
<meta name="description" content="aelve-front">
|
||||
<meta name="keywords" content="">
|
||||
<link rel="shortcut icon" href="/static/favicon.ico">
|
||||
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">
|
||||
<link href='https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons' rel="stylesheet" type="text/css">
|
||||
</head>
|
||||
<body>
|
||||
<noscript id="deferred-styles"></noscript>
|
||||
<!--vue-ssr-outlet-->
|
||||
</body>
|
||||
</html>
|
24
front/build/build-config.js
Normal file
24
front/build/build-config.js
Normal file
@ -0,0 +1,24 @@
|
||||
const path = require('path')
|
||||
const moment = require('moment')
|
||||
|
||||
const appName = '@Vert/Vue-SSR-Template'
|
||||
const clientPort = 4000
|
||||
const ssrPort = 5000
|
||||
const distPath = rootResolve('./dist')
|
||||
|
||||
const env = {
|
||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
|
||||
'process.env.BUILD_TIME': JSON.stringify(moment(new Date()).format('YYYY-MM-DD HH:mm:ss'))
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
appName,
|
||||
env,
|
||||
clientPort,
|
||||
ssrPort,
|
||||
distPath
|
||||
}
|
||||
|
||||
function rootResolve (filePath) {
|
||||
return path.resolve(__dirname, '../', filePath)
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
const path = require('path')
|
||||
const webpack = require('webpack')
|
||||
const MFS = require('memory-fs')
|
||||
const clientConfig = require('./webpack.client.config')
|
||||
const serverConfig = require('./webpack.server.config')
|
||||
|
||||
module.exports = function setupDevServer (app, cb) {
|
||||
let bundle, clientManifest
|
||||
let resolve
|
||||
let resolved = false
|
||||
const readyPromise = new Promise(r => { resolve = r })
|
||||
const ready = (...args) => {
|
||||
if (!resolved) resolve()
|
||||
cb(...args)
|
||||
}
|
||||
|
||||
// modify client config to work with hot middleware
|
||||
clientConfig.entry.app = ['webpack-hot-middleware/client', clientConfig.entry.app]
|
||||
clientConfig.output.filename = '[name].js'
|
||||
clientConfig.plugins.push(
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.NoEmitOnErrorsPlugin()
|
||||
)
|
||||
|
||||
// dev middleware
|
||||
const clientCompiler = webpack(clientConfig)
|
||||
const devMiddleware = require('webpack-dev-middleware')(clientCompiler, {
|
||||
publicPath: clientConfig.output.publicPath,
|
||||
noInfo: true
|
||||
})
|
||||
app.use(devMiddleware)
|
||||
clientCompiler.plugin('done', () => {
|
||||
const fs = devMiddleware.fileSystem
|
||||
const readFile = file => fs.readFileSync(path.join(clientConfig.output.path, file), 'utf-8')
|
||||
clientManifest = JSON.parse(readFile('vue-ssr-client-manifest.json'))
|
||||
if (bundle) {
|
||||
ready(bundle, {
|
||||
clientManifest
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// hot middleware
|
||||
app.use(require('webpack-hot-middleware')(clientCompiler))
|
||||
|
||||
// watch and update server renderer
|
||||
const serverCompiler = webpack(serverConfig)
|
||||
const mfs = new MFS()
|
||||
serverCompiler.outputFileSystem = mfs
|
||||
serverCompiler.watch({}, (err, stats) => {
|
||||
if (err) throw err
|
||||
stats = stats.toJson()
|
||||
stats.errors.forEach(err => console.error(err))
|
||||
stats.warnings.forEach(err => console.warn(err))
|
||||
const readFile = file => mfs.readFileSync(path.join(clientConfig.output.path, file), 'utf-8')
|
||||
|
||||
// read bundle generated by vue-ssr-webpack-plugin
|
||||
bundle = JSON.parse(readFile('vue-ssr-server-bundle.json'))
|
||||
if (clientManifest) {
|
||||
ready(bundle, {
|
||||
clientManifest
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return readyPromise
|
||||
}
|
30
front/build/style-loader.conf.js
Normal file
30
front/build/style-loader.conf.js
Normal file
@ -0,0 +1,30 @@
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
|
||||
const cssLoader = {
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
'vue-style-loader',
|
||||
{ loader: 'css-loader', options: { sourceMap: false, importLoaders: 1 } },
|
||||
'postcss-loader'
|
||||
]
|
||||
}
|
||||
|
||||
const stylusLoader = {
|
||||
test: /\.(styl|stylus)$/,
|
||||
use: [
|
||||
'vue-style-loader',
|
||||
{ loader: 'css-loader', options: { sourceMap: false, importLoaders: 1 } },
|
||||
'postcss-loader',
|
||||
{ loader: 'stylus-loader', options: { sourceMap: false } }
|
||||
]
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
cssLoader.use = [MiniCssExtractPlugin.loader].concat(cssLoader.use)
|
||||
stylusLoader.use = [MiniCssExtractPlugin.loader].concat(stylusLoader.use)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
cssLoader,
|
||||
stylusLoader
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
module.exports = {
|
||||
extractCSS: process.env.NODE_ENV === 'production',
|
||||
preserveWhitespace: false,
|
||||
postcss: [
|
||||
require('autoprefixer')({
|
||||
browsers: ['last 3 versions']
|
||||
})
|
||||
]
|
||||
}
|
91
front/build/webpack.base.conf.js
Normal file
91
front/build/webpack.base.conf.js
Normal file
@ -0,0 +1,91 @@
|
||||
const webpack = require('webpack')
|
||||
const { VueLoaderPlugin } = require('vue-loader')
|
||||
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
|
||||
|
||||
const { clientPort } = require('./build-config')
|
||||
const isDev = process.env.NODE_ENV === 'development'
|
||||
|
||||
module.exports = {
|
||||
output: {
|
||||
publicPath: isDev
|
||||
? `//localhost:${clientPort}/` // Please bind this hostname to 127.0.0.1 when developing.
|
||||
: '/'
|
||||
},
|
||||
|
||||
optimization: {},
|
||||
|
||||
resolve: {
|
||||
extensions: ['.js', '.ts'],
|
||||
alias: {
|
||||
'vue$': 'vue/dist/vue.esm.js'
|
||||
}
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: 'babel-loader'
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: [
|
||||
'babel-loader',
|
||||
{
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
happyPackMode: true,
|
||||
appendTsSuffixTo: [/\.vue$/]
|
||||
}
|
||||
}
|
||||
],
|
||||
exclude: /node_modules/
|
||||
},
|
||||
{
|
||||
test: /\.vue$/,
|
||||
loader: 'vue-loader'
|
||||
},
|
||||
{
|
||||
test: /\.(jade|pug)$/,
|
||||
oneOf: [
|
||||
// For vue Jade / Pug template.
|
||||
{
|
||||
resourceQuery: /^\?vue/,
|
||||
use: ['pug-plain-loader']
|
||||
},
|
||||
|
||||
// For Jade / Pug standalone files.
|
||||
{
|
||||
use: ['pug-loader']
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpg|gif|svg)$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 8192,
|
||||
name: 'img/[name].[hash:7].[ext]'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new webpack.ContextReplacementPlugin(
|
||||
/moment[\/\\]locale$/,
|
||||
/zh-cn/
|
||||
),
|
||||
|
||||
new FriendlyErrorsPlugin(),
|
||||
|
||||
new VueLoaderPlugin()
|
||||
]
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
const path = require('path')
|
||||
const webpack = require('webpack')
|
||||
const vueConfig = require('./vue-loader.config')
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin')
|
||||
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
|
||||
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
|
||||
|
||||
const isProd = process.env.NODE_ENV === 'production'
|
||||
const resolve = (file) => path.resolve(__dirname, file)
|
||||
|
||||
module.exports = {
|
||||
devtool: isProd
|
||||
? false
|
||||
: '#cheap-module-source-map',
|
||||
output: {
|
||||
path: resolve('../public'),
|
||||
publicPath: '/public/',
|
||||
filename: '[name].[chunkhash].js'
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['*', '.js', '.json', '.vue'],
|
||||
alias: {
|
||||
'assets': resolve('../assets'),
|
||||
'components': resolve('../components'),
|
||||
'examples': resolve('../pages/examples'),
|
||||
'layouts': resolve('../layouts'),
|
||||
'mixins': resolve('../mixins'),
|
||||
'pages': resolve('../pages'),
|
||||
'public': resolve('../public'),
|
||||
'router': resolve('../router'),
|
||||
'static': resolve('../static'),
|
||||
'store': resolve('../store'),
|
||||
'vue$': 'vue/dist/vue.common.js'
|
||||
}
|
||||
},
|
||||
module: {
|
||||
noParse: /es6-promise\.js$/, // avoid webpack shimming process
|
||||
rules: [
|
||||
{
|
||||
test: /\.vue$/,
|
||||
loader: 'vue-loader',
|
||||
options: vueConfig
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /node_modules/
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
loader: ['vue-style-loader', 'css-loader']
|
||||
},
|
||||
{
|
||||
test: /\.styl$/,
|
||||
loader: ['vue-style-loader', 'css-loader', 'stylus-loader']
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
query: {
|
||||
limit: 10000,
|
||||
name: 'img/[name].[hash:7].[ext]'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
performance: {
|
||||
maxEntrypointSize: 300000,
|
||||
hints: isProd ? 'warning' : false
|
||||
},
|
||||
plugins: isProd
|
||||
? [
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compress: { warnings: false }
|
||||
}),
|
||||
new ExtractTextPlugin({
|
||||
filename: 'common.[chunkhash].css'
|
||||
}),
|
||||
new OptimizeCssAssetsPlugin({
|
||||
assetNameRegExp: /\.css$/
|
||||
})
|
||||
]
|
||||
: [
|
||||
new FriendlyErrorsPlugin()
|
||||
]
|
||||
}
|
79
front/build/webpack.client.conf.js
Normal file
79
front/build/webpack.client.conf.js
Normal file
@ -0,0 +1,79 @@
|
||||
const path = require('path')
|
||||
const webpack = require('webpack')
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
const merge = require('webpack-merge')
|
||||
const VueClientPlugin = require('vue-server-renderer/client-plugin')
|
||||
|
||||
const { appName, clientPort, distPath, env } = require('./build-config')
|
||||
const { cssLoader, stylusLoader } = require('./style-loader.conf')
|
||||
const baseConfig = require('./webpack.base.conf')
|
||||
|
||||
const isProduction = process.env.NODE_ENV === 'production'
|
||||
|
||||
const webpackConfig = merge(baseConfig, {
|
||||
mode: process.env.NODE_ENV,
|
||||
|
||||
entry: {
|
||||
app: rootResolve('./client/entry.client.ts')
|
||||
},
|
||||
|
||||
output: {
|
||||
path: distPath,
|
||||
filename: `static/js/[name].${isProduction ? '[hash].' : ''}js`
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
cssLoader,
|
||||
stylusLoader
|
||||
]
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new VueClientPlugin(),
|
||||
|
||||
new webpack.DefinePlugin(Object.assign({}, env, {
|
||||
'process.env.VUE_ENV': JSON.stringify('client')
|
||||
})),
|
||||
|
||||
new HtmlWebpackPlugin({
|
||||
template: rootResolve('./index.html'),
|
||||
minify: true,
|
||||
inject: true,
|
||||
title: appName
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
if (isProduction) {
|
||||
webpackConfig.plugins.push(
|
||||
new MiniCssExtractPlugin({
|
||||
filename: 'static/css/[name].[contenthash].css',
|
||||
chunkFilename: 'static/css/[id].[contenthash].css'
|
||||
})
|
||||
)
|
||||
webpackConfig.optimization.runtimeChunk = true
|
||||
} else {
|
||||
webpackConfig.devtool = 'eval-source-map'
|
||||
webpackConfig.plugins.push(
|
||||
new webpack.HotModuleReplacementPlugin()
|
||||
)
|
||||
webpackConfig.devServer = {
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
},
|
||||
hot: true,
|
||||
quiet: true,
|
||||
compress: false,
|
||||
port: clientPort,
|
||||
proxy: {},
|
||||
historyApiFallback: true
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = webpackConfig
|
||||
|
||||
function rootResolve (filePath) {
|
||||
return path.resolve(__dirname, '../', filePath)
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
const webpack = require('webpack')
|
||||
const merge = require('webpack-merge')
|
||||
const base = require('./webpack.base.config')
|
||||
const SWPrecachePlugin = require('sw-precache-webpack-plugin')
|
||||
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
|
||||
|
||||
const config = merge(base, {
|
||||
entry: {
|
||||
app: './assets/entry-client.js'
|
||||
},
|
||||
plugins: [
|
||||
// strip dev-only code in Vue source
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
|
||||
'process.env.VUE_ENV': '"client"'
|
||||
}),
|
||||
// extract vendor chunks for better caching
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
name: 'vendor',
|
||||
minChunks: function (module) {
|
||||
// a module is extracted into the vendor chunk if...
|
||||
return (
|
||||
// it's inside node_modules
|
||||
/node_modules/.test(module.context) &&
|
||||
// and not a CSS file (due to extract-text-webpack-plugin limitation)
|
||||
!/\.css$/.test(module.request)
|
||||
)
|
||||
}
|
||||
}),
|
||||
// extract webpack runtime & manifest to avoid vendor chunk hash changing
|
||||
// on every build.
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
name: 'manifest'
|
||||
}),
|
||||
new VueSSRClientPlugin()
|
||||
]
|
||||
})
|
||||
|
||||
module.exports = config
|
65
front/build/webpack.server.conf.js
Normal file
65
front/build/webpack.server.conf.js
Normal file
@ -0,0 +1,65 @@
|
||||
const path = require('path')
|
||||
|
||||
const webpack = require('webpack')
|
||||
const merge = require('webpack-merge')
|
||||
const nodeExternals = require('webpack-node-externals')
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
const VueServerPlugin = require('vue-server-renderer/server-plugin')
|
||||
|
||||
const { distPath, env } = require('./build-config')
|
||||
const { cssLoader, stylusLoader } = require('./style-loader.conf')
|
||||
const baseConfig = require('./webpack.base.conf')
|
||||
|
||||
const webpackConfig = merge(baseConfig, {
|
||||
mode: process.env.NODE_ENV,
|
||||
|
||||
target: 'node',
|
||||
|
||||
entry: rootResolve('./client/entry.server.ts'),
|
||||
|
||||
output: {
|
||||
path: distPath,
|
||||
filename: 'server-build.js',
|
||||
libraryTarget: 'commonjs2'
|
||||
},
|
||||
|
||||
externals: nodeExternals({
|
||||
whitelist: [
|
||||
/\.css$/,
|
||||
/\.vue$/,
|
||||
/babel-polyfill/
|
||||
]
|
||||
}),
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
cssLoader, stylusLoader
|
||||
]
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new VueServerPlugin(),
|
||||
|
||||
new webpack.DefinePlugin(Object.assign({}, env, {
|
||||
'process.env.VUE_ENV': JSON.stringify('server')
|
||||
}))
|
||||
]
|
||||
})
|
||||
|
||||
switch (process.env.NODE_ENV) {
|
||||
case 'production':
|
||||
webpackConfig.plugins.push(
|
||||
new MiniCssExtractPlugin({
|
||||
filename: 'static/css/[name].[contenthash].css',
|
||||
chunkFilename: 'static/css/[id].[contenthash].css'
|
||||
})
|
||||
)
|
||||
webpackConfig.optimization.splitChunks = false
|
||||
break
|
||||
}
|
||||
|
||||
module.exports = webpackConfig
|
||||
|
||||
function rootResolve (filePath) {
|
||||
return path.resolve(__dirname, '../', filePath)
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
const webpack = require('webpack')
|
||||
const merge = require('webpack-merge')
|
||||
const base = require('./webpack.base.config')
|
||||
const nodeExternals = require('webpack-node-externals')
|
||||
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
|
||||
|
||||
module.exports = merge(base, {
|
||||
target: 'node',
|
||||
devtool: '#source-map',
|
||||
entry: './assets/entry-server.js',
|
||||
output: {
|
||||
filename: 'server-bundle.js',
|
||||
libraryTarget: 'commonjs2'
|
||||
},
|
||||
// https://webpack.js.org/configuration/externals/#externals
|
||||
// https://github.com/liady/webpack-node-externals
|
||||
externals: nodeExternals({
|
||||
// do not externalize CSS files in case we need to import it from a dep
|
||||
whitelist: [/\.css$/, /vuetify/]
|
||||
}),
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
|
||||
'process.env.VUE_ENV': '"server"'
|
||||
}),
|
||||
new VueSSRServerPlugin()
|
||||
]
|
||||
})
|
24
front/client/App.vue
Normal file
24
front/client/App.vue
Normal file
@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<v-app>
|
||||
<v-toolbar app></v-toolbar>
|
||||
<v-content>
|
||||
<v-container fluid>
|
||||
<router-view></router-view>
|
||||
</v-container>
|
||||
</v-content>
|
||||
<v-footer app></v-footer>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { AppComponent, Component } from "@vert/core";
|
||||
import axios from "axios";
|
||||
|
||||
@Component
|
||||
export default class RootComponent extends AppComponent {
|
||||
async mounted() {
|
||||
const { data: categories } = await axios.get("api/categories");
|
||||
console.log(categories);
|
||||
}
|
||||
}
|
||||
</script>
|
59
front/client/app.ts
Normal file
59
front/client/app.ts
Normal file
@ -0,0 +1,59 @@
|
||||
// TODO get rid of @vert/core package
|
||||
import { App, Injector } from '@vert/core'
|
||||
import Vue from 'vue'
|
||||
import VueRouter from 'vue-router'
|
||||
import Vuex from 'vuex'
|
||||
import Vuetify from 'vuetify'
|
||||
import { sync } from 'vuex-router-sync'
|
||||
import 'vuetify/dist/vuetify.min.css' // Ensure you are using css-loader
|
||||
|
||||
import AppComponent from './App.vue'
|
||||
import { createRouter } from './router'
|
||||
import { createStore } from './store'
|
||||
|
||||
import { GreetingService } from './service/greeting'
|
||||
import { UserService } from './service/user'
|
||||
|
||||
initVue()
|
||||
initService()
|
||||
|
||||
function initVue() {
|
||||
Vue.use(VueRouter)
|
||||
Vue.use(Vuex)
|
||||
Vue.use(Vuetify)
|
||||
}
|
||||
|
||||
function initService() {
|
||||
const Services = [
|
||||
GreetingService, UserService
|
||||
]
|
||||
const injector = Injector.create(...Services)
|
||||
Services.forEach((Service: any) => {
|
||||
App.addSingleton(Service, injector.get(Service))
|
||||
})
|
||||
}
|
||||
|
||||
function createApp() {
|
||||
const router = createRouter()
|
||||
const store = createStore()
|
||||
|
||||
sync(store, router)
|
||||
|
||||
const app = new Vue({
|
||||
router,
|
||||
store,
|
||||
render(h) {
|
||||
return h(AppComponent)
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
app,
|
||||
router,
|
||||
store
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
createApp
|
||||
}
|
68
front/client/entry.client.ts
Normal file
68
front/client/entry.client.ts
Normal file
@ -0,0 +1,68 @@
|
||||
/* tslint:disable:ordered-imports */
|
||||
|
||||
import 'reflect-metadata'
|
||||
import 'babel-polyfill'
|
||||
import Vue from 'vue'
|
||||
|
||||
import { createApp } from './app'
|
||||
|
||||
const { app, router, store } = createApp()
|
||||
|
||||
Vue.mixin({
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
const { asyncData } = this.$options
|
||||
if (typeof asyncData === 'function') {
|
||||
asyncData.call(this, {
|
||||
store: this.$store,
|
||||
route: to
|
||||
}).then(next).catch(next)
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const STATE_KEY = '__INITIAL_STATE__'
|
||||
const SSR_ENABLED = window['__SSR_IS_ON__']
|
||||
|
||||
if (window[STATE_KEY]) {
|
||||
store.replaceState(window[STATE_KEY])
|
||||
}
|
||||
|
||||
if (!SSR_ENABLED) {
|
||||
registerBeforeResolve()
|
||||
}
|
||||
|
||||
router.onReady(() => {
|
||||
if (SSR_ENABLED) {
|
||||
registerBeforeResolve()
|
||||
}
|
||||
app.$mount('#app')
|
||||
})
|
||||
|
||||
function registerBeforeResolve () {
|
||||
router.beforeResolve((to, from, next) => {
|
||||
const matched = router.getMatchedComponents(to)
|
||||
const prevMatched = router.getMatchedComponents(from)
|
||||
|
||||
let diffed = false
|
||||
const activated = matched.filter((c, i) => {
|
||||
return diffed || (diffed = (prevMatched[i] !== c))
|
||||
})
|
||||
|
||||
if (!activated.length) {
|
||||
return next()
|
||||
}
|
||||
|
||||
Promise.all(activated.map(Component => {
|
||||
const asyncDataFunc = Component['asyncData'] ||
|
||||
(Component['options'] || {})['asyncData']
|
||||
|
||||
if (typeof asyncDataFunc === 'function') {
|
||||
return asyncDataFunc({ store, route: to })
|
||||
}
|
||||
})).then(() => {
|
||||
next()
|
||||
}).catch(next)
|
||||
})
|
||||
}
|
34
front/client/entry.server.ts
Normal file
34
front/client/entry.server.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import 'reflect-metadata'
|
||||
import { createApp } from './app'
|
||||
|
||||
export default context => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { app, router, store } = createApp()
|
||||
|
||||
router.push(context.url)
|
||||
|
||||
router.onReady(() => {
|
||||
const matchedComponents = router.getMatchedComponents()
|
||||
|
||||
if (!matchedComponents.length) {
|
||||
return reject({
|
||||
code: 404,
|
||||
error: new Error('no component matched')
|
||||
})
|
||||
}
|
||||
|
||||
Promise.all(matchedComponents.map((Component) => {
|
||||
const asyncDataFunc = Component['asyncData'] || (Component['options'] || {})['asyncData']
|
||||
if (typeof asyncDataFunc === 'function') {
|
||||
return asyncDataFunc({
|
||||
store,
|
||||
route: router.currentRoute
|
||||
})
|
||||
}
|
||||
})).then(() => {
|
||||
context.state = store.state
|
||||
resolve(app)
|
||||
}).catch(reject)
|
||||
}, reject)
|
||||
})
|
||||
}
|
32
front/client/page/about/index.ts
Normal file
32
front/client/page/about/index.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { AppComponent, Component } from '@vert/core'
|
||||
|
||||
@Component
|
||||
export default class AboutPage extends AppComponent {
|
||||
private isLoading: boolean = false
|
||||
|
||||
async asyncData ({ store, route }) {
|
||||
console.log('AsyncData in about page is called.')
|
||||
|
||||
await sleep(1000)
|
||||
await store.dispatch(
|
||||
'about/setOldSaying',
|
||||
`${route.params.name}: The quick brown fox jumps over the lazy dog.`
|
||||
)
|
||||
|
||||
console.log('asyncData loaded.')
|
||||
}
|
||||
|
||||
get oldSaying (): string {
|
||||
return this.$store.getters['about/oldSaying']
|
||||
}
|
||||
|
||||
changeOldSaying () {
|
||||
this.$store.dispatch('about/setOldSaying', 'Nothing but tricks.')
|
||||
}
|
||||
}
|
||||
|
||||
function sleep (time: number) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, time)
|
||||
})
|
||||
}
|
16
front/client/page/about/index.vue
Normal file
16
front/client/page/about/index.vue
Normal file
@ -0,0 +1,16 @@
|
||||
<template lang="pug">
|
||||
div.about-page
|
||||
h1 About page.
|
||||
p(v-if="isLoading") Loading...
|
||||
p(v-else) {{oldSaying}}
|
||||
|
||||
div
|
||||
Button(@click="changeOldSaying") Change old saying
|
||||
|
||||
div
|
||||
router-link(to="/") Index
|
||||
br
|
||||
router-link(to="/about/who") About who?
|
||||
</template>
|
||||
|
||||
<script lang="ts" src="./index.ts"></script>
|
35
front/client/page/about/store/index.ts
Normal file
35
front/client/page/about/store/index.ts
Normal file
@ -0,0 +1,35 @@
|
||||
const about = {
|
||||
namespaced: true,
|
||||
|
||||
state (): IAboutState {
|
||||
return {
|
||||
oldSaying: ''
|
||||
}
|
||||
},
|
||||
|
||||
mutations: {
|
||||
SET_OLD_SAYING (state: IAboutState, payload: string) {
|
||||
state.oldSaying = payload
|
||||
}
|
||||
},
|
||||
|
||||
getters: {
|
||||
oldSaying (state: IAboutState) {
|
||||
return state.oldSaying
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
setOldSaying ({ commit }, payload: string) {
|
||||
commit('SET_OLD_SAYING', payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
about
|
||||
}
|
||||
|
||||
interface IAboutState {
|
||||
oldSaying: string
|
||||
}
|
21
front/client/page/index/index.ts
Normal file
21
front/client/page/index/index.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { AppComponent, Component } from '@vert/core'
|
||||
import { GreetingService } from '../../service/greeting'
|
||||
import { IUser, UserService } from '../../service/user'
|
||||
|
||||
@Component
|
||||
export default class AppIndex extends AppComponent {
|
||||
pageName: string = ''
|
||||
userList: IUser[] = []
|
||||
|
||||
async created () {
|
||||
this.greetingSrv.greet('Index')
|
||||
this.userList = await this.userSrv.getUserList()
|
||||
}
|
||||
|
||||
constructor (
|
||||
private greetingSrv: GreetingService,
|
||||
private userSrv: UserService
|
||||
) {
|
||||
super()
|
||||
}
|
||||
}
|
16
front/client/page/index/index.vue
Normal file
16
front/client/page/index/index.vue
Normal file
@ -0,0 +1,16 @@
|
||||
<template lang="pug">
|
||||
div.index-page
|
||||
h1 Greeting from {{pageName}}!
|
||||
|
||||
div
|
||||
p This is your user list:
|
||||
ul
|
||||
li(v-for="item in userList") Name: {{item.name}}, Age: {{item.age}}
|
||||
|
||||
p You can checkout these page too:
|
||||
ul
|
||||
li
|
||||
router-link(to="/about/me") About
|
||||
</template>
|
||||
|
||||
<script lang="ts" src="./index.ts"></script>
|
17
front/client/router/index.ts
Normal file
17
front/client/router/index.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import Router from 'vue-router'
|
||||
|
||||
function createRouter () {
|
||||
return new Router({
|
||||
mode: 'history',
|
||||
fallback: false,
|
||||
scrollBehavior: () => ({ x: 0, y: 0 }),
|
||||
routes: [
|
||||
{ path: '/', component: () => import('../page/index/index.vue') },
|
||||
{ path: '/about/:name', component: () => import('../page/about/index.vue') }
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
export {
|
||||
createRouter
|
||||
}
|
12
front/client/service/greeting.ts
Normal file
12
front/client/service/greeting.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { Injectable } from '@vert/core'
|
||||
|
||||
@Injectable()
|
||||
class GreetingService {
|
||||
greet (name: string) {
|
||||
console.log(`Hello, ${name}!`)
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
GreetingService
|
||||
}
|
21
front/client/service/user.ts
Normal file
21
front/client/service/user.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { Injectable } from '@vert/core'
|
||||
|
||||
@Injectable()
|
||||
class UserService {
|
||||
async getUserList (): Promise<IUser[]> {
|
||||
return [
|
||||
{ name: 'LancerComet', age: 10 },
|
||||
{ name: 'Wch', age: 20 }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
interface IUser {
|
||||
name: string
|
||||
age: number
|
||||
}
|
||||
|
||||
export {
|
||||
UserService,
|
||||
IUser
|
||||
}
|
15
front/client/store/index.ts
Normal file
15
front/client/store/index.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import Vuex from 'vuex'
|
||||
|
||||
import { about } from '../page/about/store'
|
||||
|
||||
function createStore () {
|
||||
return new Vuex.Store({
|
||||
modules: {
|
||||
about
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export {
|
||||
createStore
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
<template>
|
||||
<h1>Vuetify</h1>
|
||||
</template>
|
@ -1,5 +0,0 @@
|
||||
import Vuetify from './Vuetify'
|
||||
|
||||
export default {
|
||||
Vuetify
|
||||
}
|
4
front/index.d.ts
vendored
Normal file
4
front/index.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
declare module '*.vue' {
|
||||
const content: any
|
||||
export default content
|
||||
}
|
20
front/index.html
Normal file
20
front/index.html
Normal file
@ -0,0 +1,20 @@
|
||||
<!--
|
||||
<%= htmlWebpackPlugin.options.title %>
|
||||
Build time: <%= process.env.BUILD_TIME %>
|
||||
Environment: <%= process.env.NODE_ENV %>
|
||||
SSR Enabled: false
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-cmn-hans">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
<script>
|
||||
window.__SSR_IS_ON__ = false
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
@ -1,35 +0,0 @@
|
||||
const meta = require('../router/meta.json')
|
||||
|
||||
export default {
|
||||
watch: {
|
||||
'$route' () {
|
||||
this.setMeta()
|
||||
}
|
||||
},
|
||||
|
||||
created () {
|
||||
if (process.env.VUE_ENV === 'client') return
|
||||
|
||||
const metaData = meta[this.$route.path] || {}
|
||||
|
||||
this.$ssrContext.title = metaData.title
|
||||
this.$ssrContext.description = metaData.description
|
||||
this.$ssrContext.keywords = metaData.keywords
|
||||
},
|
||||
|
||||
mounted () {
|
||||
this.setMeta()
|
||||
},
|
||||
|
||||
methods: {
|
||||
setMeta () {
|
||||
if (typeof document === 'undefined') return
|
||||
|
||||
const metaData = meta[this.$route.path] || {}
|
||||
|
||||
document.title = metaData.title
|
||||
document.querySelector('meta[name="description"]').setAttribute('content', metaData.description)
|
||||
document.querySelector('meta[name="keywords"]').setAttribute('content', metaData.keywords)
|
||||
}
|
||||
}
|
||||
}
|
8509
front/package-lock.json
generated
8509
front/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,67 +1,70 @@
|
||||
{
|
||||
"name": "aelve-front",
|
||||
"description": "aelve-front",
|
||||
"author": "",
|
||||
"version": "0.0.1",
|
||||
"name": "@vert/vue-ssr-template",
|
||||
"version": "1.0.0",
|
||||
"description": "Vue project template with SSR, Vert and TypeScript support.",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "node server",
|
||||
"start": "cross-env NODE_ENV=production node server",
|
||||
"build": "rimraf dist && npm run build:client && npm run build:server",
|
||||
"build:client": "cross-env NODE_ENV=production webpack --config build/webpack.client.config.js --progress --hide-modules",
|
||||
"build:server": "cross-env NODE_ENV=production webpack --config build/webpack.server.config.js --progress --hide-modules"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0",
|
||||
"npm": ">=4.0"
|
||||
"client:dev": "cross-env NODE_ENV=development webpack-dev-server --config ./build/webpack.client.conf.js",
|
||||
"client:build": "cross-env NODE_ENV=production webpack --config ./build/webpack.client.conf.js",
|
||||
"server:dev": "cross-env NODE_ENV=development nodemon ./server/bin.js --watch server/**/*.js",
|
||||
"server:build": "cross-env NODE_ENV=production webpack --config ./build/webpack.server.conf.js",
|
||||
"build-all": "npm run client:build && npm run server:build",
|
||||
"start:dev": "concurrently \"npm run client:dev\" \"npm run server:dev\"",
|
||||
"start:prod": "cross-env NODE_ENV=production node server/bin.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-polyfill": "^6.23.0",
|
||||
"compression": "^1.6.2",
|
||||
"cross-env": "^5.0.0",
|
||||
"es6-promise": "^4.1.0",
|
||||
"express": "^4.15.2",
|
||||
"extract-text-webpack-plugin": "^2.1.0",
|
||||
"lru-cache": "^4.0.2",
|
||||
"serialize-javascript": "^1.3.0",
|
||||
"serve-favicon": "^2.3.2",
|
||||
"vue": "^2.5.3",
|
||||
"vuetify": "^1.0.0",
|
||||
"vue-router": "^2.5.3",
|
||||
"vue-server-renderer": "^2.5.3",
|
||||
"vuex": "^2.3.1",
|
||||
"vuex-router-sync": "^4.1.2"
|
||||
"@junyiz/koa-proxy-pass": "^1.2.1",
|
||||
"@vert/core": "^0.2.0",
|
||||
"axios": "^0.18.0",
|
||||
"babel-polyfill": "^6.26.0",
|
||||
"koa": "^2.5.0",
|
||||
"koa-bodyparser": "^4.2.0",
|
||||
"koa-proxies": "^0.7.0",
|
||||
"koa-proxy": "^0.9.0",
|
||||
"koa-router": "^7.4.0",
|
||||
"koa-static": "^4.0.2",
|
||||
"koa-static-server": "^1.3.4",
|
||||
"moment": "^2.22.1",
|
||||
"reflect-metadata": "^0.1.12",
|
||||
"vue": "^2.5.16",
|
||||
"vue-meta": "^1.5.0",
|
||||
"vue-router": "^3.0.1",
|
||||
"vue-server-renderer": "^2.5.16",
|
||||
"vuetify": "^1.2.1",
|
||||
"vuex": "^3.0.1",
|
||||
"vuex-router-sync": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^7.1.1",
|
||||
"babel-core": "^6.22.1",
|
||||
"babel-loader": "^7.0.0",
|
||||
"babel-plugin-transform-runtime": "^6.22.0",
|
||||
"babel-preset-env": "^1.6.0",
|
||||
"babel-loader": "^7.1.4",
|
||||
"babel-plugin-transform-runtime": "^6.23.0",
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"babel-preset-stage-2": "^6.24.1",
|
||||
"babel-plugin-transform-imports": "^1.4.1",
|
||||
"optimize-css-assets-webpack-plugin": "^2.0.0",
|
||||
"babel-register": "^6.22.0",
|
||||
"css-loader": "^0.28.0",
|
||||
"eventsource-polyfill": "^0.9.6",
|
||||
"file-loader": "^0.11.1",
|
||||
"friendly-errors-webpack-plugin": "^1.6.1",
|
||||
"highlight.js": "^9.8.0",
|
||||
"html-webpack-plugin": "^2.24.1",
|
||||
"pug": "^2.0.0-beta3",
|
||||
"rimraf": "^2.6.1",
|
||||
"script-ext-html-webpack-plugin": "^1.3.4",
|
||||
"style-loader": "^0.18.1",
|
||||
"concurrently": "^3.5.1",
|
||||
"cross-env": "^5.2.0",
|
||||
"css-loader": "^0.28.11",
|
||||
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
||||
"file-loader": "^1.1.11",
|
||||
"friendly-errors-webpack-plugin": "^1.7.0",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"memory-fs": "^0.4.1",
|
||||
"mini-css-extract-plugin": "^0.4.0",
|
||||
"nodemon": "^1.17.5",
|
||||
"postcss-loader": "^2.1.5",
|
||||
"pug": "^2.0.3",
|
||||
"pug-loader": "^2.4.0",
|
||||
"pug-plain-loader": "^1.0.0",
|
||||
"stylus": "^0.54.5",
|
||||
"stylus-loader": "^3.0.1",
|
||||
"sw-precache-webpack-plugin": "^0.9.1",
|
||||
"url-loader": "^0.5.8",
|
||||
"vue-loader": "^12.0.3",
|
||||
"vue-style-loader": "^3.0.0",
|
||||
"vue-template-compiler": "^2.5.3",
|
||||
"webpack": "^2.2.1",
|
||||
"webpack-dev-middleware": "^1.10.1",
|
||||
"webpack-hot-middleware": "^2.17.1",
|
||||
"webpack-merge": "^4.0.0",
|
||||
"webpack-node-externals": "^1.5.4"
|
||||
"stylus-loader": "^3.0.2",
|
||||
"ts-loader": "^4.4.2",
|
||||
"typescript": "^2.9.2",
|
||||
"url-loader": "^1.0.1",
|
||||
"vue-loader": "^15.2.4",
|
||||
"vue-style-loader": "^4.1.0",
|
||||
"vue-template-compiler": "^2.5.16",
|
||||
"webpack": "^4.6.0",
|
||||
"webpack-cli": "^2.0.14",
|
||||
"webpack-dev-server": "^3.1.3",
|
||||
"webpack-merge": "^4.1.2",
|
||||
"webpack-node-externals": "^1.7.2"
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +0,0 @@
|
||||
<template>
|
||||
<v-layout>
|
||||
<v-flex text-xs-center>
|
||||
<img src="/static/v.png" alt="Vuetify.js" class="mb-5" />
|
||||
<blockquote class="blockquote">
|
||||
“First, solve the problem. Then, write the code.”
|
||||
<footer>
|
||||
<small>
|
||||
<em>—John Johnson</em>
|
||||
</small>
|
||||
</footer>
|
||||
</blockquote>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</template>
|
@ -1,22 +0,0 @@
|
||||
<template>
|
||||
<v-layout column justify-center align-center>
|
||||
<v-flex xs12 sm8 md6>
|
||||
<div class="text-xs-center">
|
||||
<img src="/static/v.png" alt="Vuetify.js" class="mb-5" />
|
||||
</div>
|
||||
<v-card>
|
||||
<v-card-text>
|
||||
<p>Welcome to the Webpack SSR template.</p>
|
||||
<p>Vuetify is a progressive Material Design component framework for Vue.js. It was designed to empower developers to create amazing applications. For more information on Vuetify, check out the <a href="https://vuetifyjs.com" target="_blank">documentation</a>. If you have questions, please join the official <a href="https://gitter.im/vuetifyjs/Lobby" target="_blank" title="chat">gitter</a>. Find a bug? Report it on the github <a href="https://github.com/vuetifyjs/vuetify/issues" target="_blank" title="contribute">issue board</a>.</p>
|
||||
<p>Thank you for developing with Vuetify and I look forward to bringing more exciting features in the future.</p>
|
||||
<div class="text-xs-right">
|
||||
<em><small>— John Leider</small></em>
|
||||
</div>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn color="primary" flat router to="/inspire">Continue</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</template>
|
11
front/postcss.config.js
Normal file
11
front/postcss.config.js
Normal file
@ -0,0 +1,11 @@
|
||||
const autoprefixer = require('autoprefixer')
|
||||
|
||||
const plugins = [
|
||||
autoprefixer({
|
||||
browsers: ['last 3 versions', '> 2%', 'ie >= 9', 'Firefox >= 30', 'Chrome >= 30']
|
||||
})
|
||||
]
|
||||
|
||||
module.exports = {
|
||||
plugins
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
{
|
||||
"/home": "/"
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
import Vue from 'vue'
|
||||
import Router from 'vue-router'
|
||||
|
||||
// The meta data for your routes
|
||||
const meta = require('./meta.json')
|
||||
|
||||
// Function to create routes
|
||||
// Is default lazy but can be changed
|
||||
function route (path, view) {
|
||||
return {
|
||||
path: path,
|
||||
meta: meta[path],
|
||||
component: resolve => import(`pages/${view}View.vue`).then(resolve)
|
||||
}
|
||||
}
|
||||
|
||||
Vue.use(Router)
|
||||
|
||||
export function createRouter () {
|
||||
const router = new Router({
|
||||
base: __dirname,
|
||||
mode: 'history',
|
||||
scrollBehavior: () => ({ y: 0 }),
|
||||
routes: [
|
||||
route('/', 'Welcome'),
|
||||
route('/inspire', 'Inspire'),
|
||||
// Global redirect for 404
|
||||
{ path: '*', redirect: '/' }
|
||||
]
|
||||
})
|
||||
|
||||
// Send a pageview to Google Analytics
|
||||
router.beforeEach((to, from, next) => {
|
||||
if (typeof ga !== 'undefined') {
|
||||
ga('set', 'page', to.path)
|
||||
ga('send', 'pageview')
|
||||
}
|
||||
next()
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
{
|
||||
"/": {
|
||||
"title": "Vue.js 2 Material Component Framework",
|
||||
"description": "My Vuetify.js Project",
|
||||
"keywords": "vue 2.0, vuetify"
|
||||
},
|
||||
"/inspire": {
|
||||
"title": "Create something amazing",
|
||||
"description": "",
|
||||
"keywords": ""
|
||||
}
|
||||
}
|
144
front/server.js
144
front/server.js
@ -1,144 +0,0 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const LRU = require('lru-cache')
|
||||
const express = require('express')
|
||||
const favicon = require('serve-favicon')
|
||||
const compression = require('compression')
|
||||
const resolve = file => path.resolve(__dirname, file)
|
||||
const { createBundleRenderer } = require('vue-server-renderer')
|
||||
const redirects = require('./router/301.json')
|
||||
|
||||
const isProd = process.env.NODE_ENV === 'production'
|
||||
const useMicroCache = process.env.MICRO_CACHE !== 'false'
|
||||
const serverInfo =
|
||||
`express/${require('express/package.json').version} ` +
|
||||
`vue-server-renderer/${require('vue-server-renderer/package.json').version}`
|
||||
|
||||
const app = express()
|
||||
|
||||
const template = fs.readFileSync(resolve('./assets/index.template.html'), 'utf-8')
|
||||
|
||||
function createRenderer (bundle, options) {
|
||||
// https://github.com/vuejs/vue/blob/dev/packages/vue-server-renderer/README.md#why-use-bundlerenderer
|
||||
return createBundleRenderer(bundle, Object.assign(options, {
|
||||
template,
|
||||
// for component caching
|
||||
cache: LRU({
|
||||
max: 1000,
|
||||
maxAge: 1000 * 60 * 15
|
||||
}),
|
||||
// this is only needed when vue-server-renderer is npm-linked
|
||||
basedir: resolve('./public'),
|
||||
// recommended for performance
|
||||
runInNewContext: false
|
||||
}))
|
||||
}
|
||||
|
||||
let renderer
|
||||
let readyPromise
|
||||
if (isProd) {
|
||||
// In production: create server renderer using built server bundle.
|
||||
// The server bundle is generated by vue-ssr-webpack-plugin.
|
||||
const bundle = require('./public/vue-ssr-server-bundle.json')
|
||||
// The client manifests are optional, but it allows the renderer
|
||||
// to automatically infer preload/prefetch links and directly add <script>
|
||||
// tags for any async chunks used during render, avoiding waterfall requests.
|
||||
const clientManifest = require('./public/vue-ssr-client-manifest.json')
|
||||
renderer = createRenderer(bundle, {
|
||||
clientManifest
|
||||
})
|
||||
} else {
|
||||
// In development: setup the dev server with watch and hot-reload,
|
||||
// and create a new renderer on bundle / index template update.
|
||||
readyPromise = require('./build/setup-dev-server')(app, (bundle, options) => {
|
||||
renderer = createRenderer(bundle, options)
|
||||
})
|
||||
}
|
||||
|
||||
const serve = (path, cache) => express.static(resolve(path), {
|
||||
maxAge: cache && isProd ? 60 * 60 * 24 * 30 : 0
|
||||
})
|
||||
|
||||
app.use(compression({ threshold: 0 }))
|
||||
app.use(favicon('./static/favicon.ico'))
|
||||
app.use('/static', serve('./static', true))
|
||||
app.use('/public', serve('./public', true))
|
||||
app.use('/static/robots.txt', serve('./robots.txt'))
|
||||
|
||||
app.get('/sitemap.xml', (req, res) => {
|
||||
res.setHeader("Content-Type", "text/xml")
|
||||
res.sendFile(resolve('./static/sitemap.xml'))
|
||||
})
|
||||
|
||||
// 301 redirect for changed routes
|
||||
Object.keys(redirects).forEach(k => {
|
||||
app.get(k, (req, res) => res.redirect(301, redirects[k]))
|
||||
})
|
||||
|
||||
// 1-second microcache.
|
||||
// https://www.nginx.com/blog/benefits-of-microcaching-nginx/
|
||||
const microCache = LRU({
|
||||
max: 100,
|
||||
maxAge: 1000
|
||||
})
|
||||
|
||||
// since this app has no user-specific content, every page is micro-cacheable.
|
||||
// if your app involves user-specific content, you need to implement custom
|
||||
// logic to determine whether a request is cacheable based on its url and
|
||||
// headers.
|
||||
const isCacheable = req => useMicroCache
|
||||
|
||||
function render (req, res) {
|
||||
const s = Date.now()
|
||||
|
||||
res.setHeader("Content-Type", "text/html")
|
||||
res.setHeader("Server", serverInfo)
|
||||
|
||||
const handleError = err => {
|
||||
if (err && err.code === 404) {
|
||||
res.status(404).end('404 | Page Not Found')
|
||||
} else {
|
||||
// Render Error Page or Redirect
|
||||
res.status(500).end('500 | Internal Server Error')
|
||||
console.error(`error during render : ${req.url}`)
|
||||
console.error(err.stack)
|
||||
}
|
||||
}
|
||||
|
||||
const cacheable = isCacheable(req)
|
||||
if (cacheable) {
|
||||
const hit = microCache.get(req.url)
|
||||
if (hit) {
|
||||
if (!isProd) {
|
||||
console.log(`cache hit!`)
|
||||
}
|
||||
return res.end(hit)
|
||||
}
|
||||
}
|
||||
|
||||
const context = {
|
||||
title: 'Vuetify', // default title
|
||||
url: req.url
|
||||
}
|
||||
renderer.renderToString(context, (err, html) => {
|
||||
if (err) {
|
||||
return handleError(err)
|
||||
}
|
||||
res.end(html)
|
||||
if (cacheable) {
|
||||
microCache.set(req.url, html)
|
||||
}
|
||||
if (!isProd) {
|
||||
console.log(`whole request: ${Date.now() - s}ms`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
app.get('*', isProd ? render : (req, res) => {
|
||||
readyPromise.then(() => render(req, res))
|
||||
})
|
||||
|
||||
const port = process.env.PORT || 8080
|
||||
app.listen(port, '0.0.0.0', () => {
|
||||
console.log(`server started at localhost:${port}`)
|
||||
})
|
38
front/server/bin.js
Normal file
38
front/server/bin.js
Normal file
@ -0,0 +1,38 @@
|
||||
const path = require('path')
|
||||
// TODO заменить на express
|
||||
const Koa = require('koa')
|
||||
const serve = require('koa-static')
|
||||
const bodyparser = require('koa-bodyparser')
|
||||
|
||||
const { ssrPort } = require('../build/build-config')
|
||||
const proxy = require('koa-proxy')
|
||||
|
||||
const config = require('./config.json')
|
||||
|
||||
const app = new Koa()
|
||||
app.use(proxy({
|
||||
host: config.apiUrl,
|
||||
match: /^\/api\//,
|
||||
// map: function (path) {
|
||||
// return path.replace('/api', '')
|
||||
// }
|
||||
}))
|
||||
app.use(bodyparser())
|
||||
|
||||
let ssrRouter = null
|
||||
switch (process.env.NODE_ENV) {
|
||||
case 'production':
|
||||
app.use(serve(path.resolve(__dirname, '../dist'), {
|
||||
index: 'does-not-exist.html' // Set to anything but 'index.html' to use ssr.
|
||||
}))
|
||||
ssrRouter = require('./ssr.prod.js')
|
||||
break
|
||||
|
||||
case 'development':
|
||||
ssrRouter = require('./ssr.dev.js')
|
||||
break
|
||||
}
|
||||
app.use(ssrRouter)
|
||||
app.listen(ssrPort, () => {
|
||||
console.log(`[Info] Server is on at ${ssrPort}.`)
|
||||
})
|
14
front/server/ssr.config.js
Normal file
14
front/server/ssr.config.js
Normal file
@ -0,0 +1,14 @@
|
||||
const { appName } = require('../build/build-config')
|
||||
const moment = require('moment')
|
||||
|
||||
function templateEnvs () {
|
||||
return {
|
||||
title: appName,
|
||||
isSSR: true,
|
||||
renderTime: moment(new Date()).format('YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
templateEnvs
|
||||
}
|
75
front/server/ssr.dev.js
Normal file
75
front/server/ssr.dev.js
Normal file
@ -0,0 +1,75 @@
|
||||
const axios = require('axios')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const MemoryFs = require('memory-fs')
|
||||
const webpack = require('webpack')
|
||||
const VueServerRenderer = require('vue-server-renderer')
|
||||
|
||||
const serverConfig = require('../build/webpack.server.conf')
|
||||
const serverCompiler = webpack(serverConfig)
|
||||
const { clientPort } = require('../build/build-config')
|
||||
const { templateEnvs } = require('./ssr.config')
|
||||
|
||||
const mfs = new MemoryFs()
|
||||
serverCompiler.outputFileSystem = mfs
|
||||
|
||||
let bundle = null
|
||||
serverCompiler.watch({}, (err, stats) => {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
stats = stats.toJson()
|
||||
stats.errors.forEach(err => console.log(err))
|
||||
stats.warnings.forEach(warn => console.log(warn))
|
||||
|
||||
const bundlePath = path.join(serverConfig.output.path, 'vue-ssr-server-bundle.json')
|
||||
bundle = JSON.parse(mfs.readFileSync(bundlePath, 'utf-8'))
|
||||
})
|
||||
|
||||
const urlsToSkip = [
|
||||
'/favicon.ico'
|
||||
]
|
||||
|
||||
module.exports = async function handler (ctx) {
|
||||
const url = ctx.path
|
||||
|
||||
// Skip favicon.
|
||||
if (urlsToSkip.indexOf(url) > -1) {
|
||||
ctx.body = ''
|
||||
return
|
||||
}
|
||||
|
||||
if (!bundle) {
|
||||
ctx.body = 'Please wait...'
|
||||
return
|
||||
}
|
||||
|
||||
const clientManifestUrl = `http://${ctx.hostname}:${clientPort}/vue-ssr-client-manifest.json`
|
||||
let clientManifest = null
|
||||
try {
|
||||
const { data } = await axios.get(clientManifestUrl)
|
||||
clientManifest = data
|
||||
} catch (error) {
|
||||
console.error(`[Error] Failed to get ${clientManifestUrl}:`, error)
|
||||
ctx.body = error.message || 'Failed to get client manifest json'
|
||||
return
|
||||
}
|
||||
|
||||
const renderer = VueServerRenderer.createBundleRenderer(bundle, {
|
||||
clientManifest,
|
||||
template: fs.readFileSync(path.resolve(__dirname, './template.html'), 'utf-8'),
|
||||
runInNewContext: false
|
||||
})
|
||||
|
||||
const context = Object.assign(templateEnvs(), {
|
||||
url
|
||||
})
|
||||
|
||||
try {
|
||||
ctx.body = await renderer.renderToString(context)
|
||||
} catch (error) {
|
||||
console.error('[Error] SSR render error:', error)
|
||||
ctx.body = error.message || 'SSR unknown renderer error'
|
||||
}
|
||||
}
|
||||
|
23
front/server/ssr.prod.js
Normal file
23
front/server/ssr.prod.js
Normal file
@ -0,0 +1,23 @@
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const { createBundleRenderer } = require('vue-server-renderer')
|
||||
const { templateEnvs } = require('./ssr.config')
|
||||
|
||||
const bundle = require('../dist/vue-ssr-server-bundle.json')
|
||||
const renderer = createBundleRenderer(bundle, {
|
||||
template: fs.readFileSync(path.resolve(__dirname, './template.html'), 'utf-8'),
|
||||
clientManifest: require('../dist/vue-ssr-client-manifest.json'),
|
||||
runInNewContext: false
|
||||
})
|
||||
|
||||
module.exports = async function handler (ctx) {
|
||||
const context = Object.assign(templateEnvs(), {
|
||||
url: ctx.path
|
||||
})
|
||||
|
||||
try {
|
||||
ctx.body = await renderer.renderToString(context)
|
||||
} catch (error) {
|
||||
ctx.body = error
|
||||
}
|
||||
}
|
22
front/server/template.html
Normal file
22
front/server/template.html
Normal file
@ -0,0 +1,22 @@
|
||||
<!--
|
||||
{{title}}
|
||||
Build time: {{renderTime}}
|
||||
Environment: {{process.env.NODE_ENV}}
|
||||
SSR Enabled: {{isSSR}}
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-cmn-hans">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{title}}</title>
|
||||
<script>
|
||||
window.__SSR_IS_ON__ = true
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app">
|
||||
<!--vue-ssr-outlet-->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Binary file not shown.
Before Width: | Height: | Size: 1.5 KiB |
Binary file not shown.
Before Width: | Height: | Size: 15 KiB |
@ -1,2 +0,0 @@
|
||||
User-agent: *
|
||||
Disallow:
|
@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
|
||||
<url>
|
||||
<loc>/</loc>
|
||||
<lastmod>2017-05-13</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
</urlset>
|
Binary file not shown.
Before Width: | Height: | Size: 5.5 KiB |
@ -1,16 +0,0 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
export function createStore () {
|
||||
return new Vuex.Store({
|
||||
state: {},
|
||||
|
||||
actions: {},
|
||||
|
||||
mutations: {},
|
||||
|
||||
getters: {}
|
||||
})
|
||||
}
|
20
front/tsconfig.json
Normal file
20
front/tsconfig.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"allowJs": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"moduleResolution": "node",
|
||||
"module": "esnext",
|
||||
"target": "es2016",
|
||||
"sourceMap": true,
|
||||
"skipLibCheck": true,
|
||||
"skipDefaultLibCheck": true,
|
||||
"lib": [
|
||||
"dom",
|
||||
"es5",
|
||||
"es2015",
|
||||
"es2016"
|
||||
]
|
||||
}
|
||||
}
|
29
front/tslint.json
Normal file
29
front/tslint.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"defaultSeverity": "error",
|
||||
"extends": [
|
||||
"tslint:recommended"
|
||||
],
|
||||
"jsRules": {},
|
||||
"rules": {
|
||||
"arrow-parens": false,
|
||||
"comment-format": [true, "check-space"],
|
||||
"max-classes-per-file": [false],
|
||||
"member-access": false,
|
||||
"member-ordering": false,
|
||||
"no-console": false,
|
||||
"no-string-literal": false,
|
||||
"no-shadowed-variable": false,
|
||||
"no-angle-bracket-type-assertion": false,
|
||||
"no-unused-expression": [true, "allow-fast-null-checks"],
|
||||
"no-var-requires": false,
|
||||
"object-literal-sort-keys": false,
|
||||
"only-arrow-functions": false,
|
||||
"quotemark": [true, "single"],
|
||||
"semicolon": [true, "never"],
|
||||
"space-before-function-paren": true,
|
||||
"whitespace": [true, "check-module"],
|
||||
"trailing-comma": false,
|
||||
"variable-name": false
|
||||
},
|
||||
"rulesDirectory": []
|
||||
}
|
4658
front/yarn.lock
4658
front/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user