mirror of
https://github.com/aelve/guide.git
synced 2024-11-25 07:52:52 +03:00
Front/structure refactor and building reconfigure (#277)
* DefferedPromise refactor * Moved defferedPromise to utils folder * Base url moved to app file * Store modules states rewrittten according to vue doc recommendations * Deffered promise usage moved * removed unused packages in entries * Structure refactor, easier building, prod building configured, tsconfig reconfigure removed useless packages * Update front/index.html Co-Authored-By: avele <34437766+avele@users.noreply.github.com> * Update front/postcss.config.js Co-Authored-By: avele <34437766+avele@users.noreply.github.com> * Comment rewritten
This commit is contained in:
parent
3c74057616
commit
469b5b5790
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"presets": [
|
|
||||||
["@babel/env", {
|
|
||||||
"targets": {
|
|
||||||
"browsers": ["last 3 versions", "> 2%", "ie >= 10", "Firefox >= 30", "Chrome >= 30"]
|
|
||||||
},
|
|
||||||
"modules": false,
|
|
||||||
"loose": true,
|
|
||||||
"useBuiltIns": "entry"
|
|
||||||
}]
|
|
||||||
],
|
|
||||||
"plugins": [
|
|
||||||
"@babel/plugin-transform-runtime",
|
|
||||||
"@babel/plugin-syntax-dynamic-import"
|
|
||||||
],
|
|
||||||
"env": {
|
|
||||||
"test": {
|
|
||||||
"plugins": ["transform-es2015-modules-commonjs"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +1,16 @@
|
|||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
- `client:dev` - Start developing environment of client content.
|
- `dev` - Start developing environment (starts both server and setup client middleware for server).
|
||||||
- `client:build` - Build client content.
|
- `build` - Builds to /dist folder. Compiles (from ts to js) server right in dist folder, client files compiled in /dist/src.
|
||||||
- `server:dev` - Start developing environment of ssr server.
|
|
||||||
- `server:build` - Build ssr content.
|
## Deploy process
|
||||||
- `build-all` - Run `client:build` and `server:build` at same time. Please run this command for deployment.
|
Node version >= 11, because used fs async methods which experimental now
|
||||||
- `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.
|
- `git pull`
|
||||||
|
- `npm i`
|
||||||
|
- `npm run build`
|
||||||
|
- `set NODE_ENV=production`, also you can specify port by `set PORT=%port_number%`
|
||||||
|
- `cd dist`
|
||||||
|
- `npm i`
|
||||||
|
- `node run server`
|
||||||
|
|
21
front/babel.config.js
Normal file
21
front/babel.config.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
['@babel/env', {
|
||||||
|
targets: {
|
||||||
|
browsers: ['last 3 versions', '> 2%', 'ie >= 10', 'Firefox >= 30', 'Chrome >= 30']
|
||||||
|
},
|
||||||
|
modules: false,
|
||||||
|
loose: true,
|
||||||
|
useBuiltIns: 'entry'
|
||||||
|
}]
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
'@babel/plugin-transform-runtime',
|
||||||
|
'@babel/plugin-syntax-dynamic-import'
|
||||||
|
],
|
||||||
|
env: {
|
||||||
|
test: {
|
||||||
|
plugins: ['transform-es2015-modules-commonjs']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,27 +0,0 @@
|
|||||||
const path = require('path')
|
|
||||||
const moment = require('moment')
|
|
||||||
const axios = require('axios')
|
|
||||||
|
|
||||||
const appName = 'Aelve Guide'
|
|
||||||
const clientPort = 4000
|
|
||||||
const ssrPort = 5000
|
|
||||||
const distPath = rootResolve('./dist')
|
|
||||||
|
|
||||||
axios.defaults.baseURL = `http://localhost:${ssrPort}`
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
127
front/build/setupDevServer.ts
Normal file
127
front/build/setupDevServer.ts
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
import MemoryFs from 'memory-fs'
|
||||||
|
import webpack from 'webpack'
|
||||||
|
import { createBundleRenderer } from 'vue-server-renderer'
|
||||||
|
import koaWebpack from 'koa-webpack'
|
||||||
|
import DeferredPromise from '../utils/DeferredPromise'
|
||||||
|
|
||||||
|
const fsAsync = fs.promises
|
||||||
|
|
||||||
|
MemoryFs.prototype.readFileAsync = async function (...args: any) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.readFile(...args, (err: any, result: string | Buffer) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resolve(result)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
import serverConfig from '../build/webpack.server.conf'
|
||||||
|
import clientConfig from '../build/webpack.client.conf'
|
||||||
|
const serverCompiler = webpack(serverConfig)
|
||||||
|
const clientCompiler = webpack(clientConfig)
|
||||||
|
|
||||||
|
const mfs = new MemoryFs()
|
||||||
|
|
||||||
|
const clientManifestFileName = 'vue-ssr-client-manifest.json'
|
||||||
|
const serverBundleFileName = 'vue-ssr-server-bundle.json'
|
||||||
|
|
||||||
|
// TODO add icon and refactor, change favicon serving
|
||||||
|
const urlsToSkip = [
|
||||||
|
'/favicon.ico'
|
||||||
|
]
|
||||||
|
|
||||||
|
let bundle: object = null
|
||||||
|
let clientManifest: object = null
|
||||||
|
let renderer = null
|
||||||
|
let template: string = null
|
||||||
|
|
||||||
|
export default async function setupDevServer (app): Promise<void> {
|
||||||
|
const promise = new DeferredPromise()
|
||||||
|
|
||||||
|
serverCompiler.outputFileSystem = mfs
|
||||||
|
serverCompiler.watch({}, async (err: any, stats: any) => {
|
||||||
|
logWebpack(err, stats)
|
||||||
|
const bundlePath = path.join(serverConfig.output.path, serverBundleFileName)
|
||||||
|
bundle = JSON.parse(await mfs.readFileAsync(bundlePath, 'utf-8'))
|
||||||
|
updateRenderer(promise.resolve)
|
||||||
|
})
|
||||||
|
|
||||||
|
await Promise.all([setupClientDevMiddlware(app, promise.resolve), setupTemplate()])
|
||||||
|
app.use(handler)
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
return promise
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setupClientDevMiddlware (app, resolveSetup) {
|
||||||
|
const clientDevMiddleware = await koaWebpack({
|
||||||
|
compiler: clientCompiler
|
||||||
|
})
|
||||||
|
clientCompiler.hooks.done.tap('updateRender', async (stats: any) => {
|
||||||
|
logWebpack(undefined, stats)
|
||||||
|
const manifestPath = path.join(clientConfig.output.path, clientManifestFileName)
|
||||||
|
const fileSystem = clientDevMiddleware.devMiddleware.fileSystem
|
||||||
|
clientManifest = JSON.parse(await fileSystem.readFileAsync(manifestPath, 'utf-8'))
|
||||||
|
updateRenderer(resolveSetup)
|
||||||
|
})
|
||||||
|
app.use(clientDevMiddleware)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setupTemplate () {
|
||||||
|
const templatePath = path.resolve(__dirname, '../index.html')
|
||||||
|
template = await fsAsync.readFile(templatePath, 'utf-8') as string
|
||||||
|
}
|
||||||
|
|
||||||
|
function logWebpack (err: Error, stats) {
|
||||||
|
if (err) {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
stats = stats.toJson()
|
||||||
|
stats.errors.forEach((err: any) => console.log(err))
|
||||||
|
stats.warnings.forEach((warn: any) => console.log(warn))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateRenderer (resolveSetup: () => void) {
|
||||||
|
if (!bundle || !clientManifest || !template) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer = createBundleRenderer(bundle, {
|
||||||
|
clientManifest,
|
||||||
|
template,
|
||||||
|
runInNewContext: false
|
||||||
|
})
|
||||||
|
|
||||||
|
if (resolveSetup) {
|
||||||
|
resolveSetup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handler (ctx) {
|
||||||
|
const { url } = ctx
|
||||||
|
|
||||||
|
// TODO add favicon skip favicon.
|
||||||
|
if (urlsToSkip.includes(url)) {
|
||||||
|
ctx.body = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bundle || !clientManifest || !renderer) {
|
||||||
|
ctx.body = 'Please wait...'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ctx.response.header['Content-Type'] = 'text/html'
|
||||||
|
ctx.body = await renderer.renderToString({ url })
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Error] SSR render error:', error)
|
||||||
|
ctx.body = 500
|
||||||
|
ctx.body = error.message || 'Unknown Internal Server Error'
|
||||||
|
}
|
||||||
|
}
|
36
front/build/setupProdServer.ts
Normal file
36
front/build/setupProdServer.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// TODO move imports here and make no dynamic
|
||||||
|
import path from 'path'
|
||||||
|
import fs from 'fs'
|
||||||
|
import { createBundleRenderer } from 'vue-server-renderer'
|
||||||
|
// TODO move prefix to config
|
||||||
|
import bundle from '../src/vue-ssr-server-bundle.json'
|
||||||
|
import clientManifest from '../src/vue-ssr-client-manifest.json'
|
||||||
|
import serve from 'koa-static'
|
||||||
|
import koaMount from 'koa-mount'
|
||||||
|
|
||||||
|
const fsAsync = fs.promises
|
||||||
|
export default async function setupProdServer (app) {
|
||||||
|
|
||||||
|
const template = await fsAsync.readFile(path.resolve(__dirname, '../src/index.html'), 'utf-8') as string
|
||||||
|
|
||||||
|
const renderer = createBundleRenderer(bundle, {
|
||||||
|
template,
|
||||||
|
clientManifest,
|
||||||
|
runInNewContext: false
|
||||||
|
})
|
||||||
|
|
||||||
|
async function handler (ctx) {
|
||||||
|
const context = {
|
||||||
|
url: ctx.path
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ctx.body = await renderer.renderToString(context)
|
||||||
|
} catch (error) {
|
||||||
|
ctx.body = error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app.use(koaMount('/src', serve(path.resolve(__dirname, '../src/'))))
|
||||||
|
app.use(handler)
|
||||||
|
}
|
@ -1,30 +0,0 @@
|
|||||||
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,68 +1,156 @@
|
|||||||
const webpack = require('webpack')
|
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
const { DefinePlugin } = require('webpack')
|
||||||
const { VueLoaderPlugin } = require('vue-loader')
|
const { VueLoaderPlugin } = require('vue-loader')
|
||||||
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
|
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin')
|
||||||
const TSLintPlugin = require('tslint-webpack-plugin')
|
|
||||||
const { cssLoader, stylusLoader } = require('./style-loader.conf')
|
|
||||||
|
|
||||||
const { clientPort, ssrPort } = require('./build-config')
|
const isProduction = process.env.NODE_ENV === 'production'
|
||||||
const isDev = process.env.NODE_ENV === 'development'
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
mode: isDev ? 'development' : 'production',
|
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
mode: isProduction ? 'production' : 'development',
|
||||||
|
devtool: isProduction ? false : 'cheap-module-source-map',
|
||||||
output: {
|
output: {
|
||||||
publicPath: isDev
|
path: path.resolve(__dirname, '../dist/src'),
|
||||||
? `//localhost:${clientPort}/` // Please bind this hostname to 127.0.0.1 when developing.
|
publicPath: '/src/',
|
||||||
: '/'
|
// filename: '[name].[hash].js'
|
||||||
|
filename: '[name].js'
|
||||||
},
|
},
|
||||||
|
|
||||||
optimization: {},
|
|
||||||
|
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['.js', '.ts'],
|
extensions: [
|
||||||
|
'.mjs',
|
||||||
|
'.js',
|
||||||
|
'.jsx',
|
||||||
|
'.ts',
|
||||||
|
'.tsx'
|
||||||
|
],
|
||||||
alias: {
|
alias: {
|
||||||
client: path.resolve(__dirname, '../client/')
|
client: path.resolve(__dirname, '../client/'),
|
||||||
}
|
utils: path.resolve(__dirname, '../utils/')
|
||||||
|
},
|
||||||
|
modules: ['node_modules']
|
||||||
|
},
|
||||||
|
stats: {
|
||||||
|
modules: false
|
||||||
},
|
},
|
||||||
|
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
cssLoader,
|
{
|
||||||
stylusLoader,
|
test: /\.vue$/,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'vue-loader',
|
||||||
|
options: {
|
||||||
|
compilerOptions: {
|
||||||
|
preserveWhitespace: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
test: /\.js$/,
|
test: /\.js$/,
|
||||||
exclude: /node_modules/,
|
|
||||||
use: {
|
use: {
|
||||||
loader: 'babel-loader'
|
loader: 'babel-loader'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.tsx?$/,
|
test: /\.ts$/,
|
||||||
use: [
|
use: [
|
||||||
'babel-loader',
|
{
|
||||||
|
loader: 'babel-loader'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
loader: 'ts-loader',
|
loader: 'ts-loader',
|
||||||
options: {
|
options: {
|
||||||
happyPackMode: true,
|
transpileOnly: true,
|
||||||
appendTsSuffixTo: [/\.vue$/]
|
appendTsSuffixTo: [/\.vue$/]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
exclude: /node_modules/
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.vue$/,
|
test: /\.tsx$/,
|
||||||
loader: 'vue-loader'
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'babel-loader'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loader: 'ts-loader',
|
||||||
|
options: {
|
||||||
|
transpileOnly: true,
|
||||||
|
appendTsxSuffixTo: [/\.vue$/]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.(png|jpg|gif|svg|woff|woff2|eot|ttf)$/,
|
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
|
||||||
use: [
|
use: [
|
||||||
{
|
{
|
||||||
loader: 'url-loader',
|
loader: 'url-loader',
|
||||||
options: {
|
options: {
|
||||||
limit: 8192,
|
limit: 4096,
|
||||||
name: 'img/[name].[hash:7].[ext]'
|
fallback: {
|
||||||
|
loader: 'file-loader',
|
||||||
|
options: {
|
||||||
|
name: 'img/[name].[hash:8].[ext]'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.css$/,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'vue-style-loader',
|
||||||
|
options: {
|
||||||
|
sourceMap: false,
|
||||||
|
shadowMode: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loader: 'css-loader',
|
||||||
|
options: {
|
||||||
|
sourceMap: false,
|
||||||
|
importLoaders: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loader: 'postcss-loader',
|
||||||
|
options: {
|
||||||
|
sourceMap: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.scss$/,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'vue-style-loader',
|
||||||
|
options: {
|
||||||
|
sourceMap: false,
|
||||||
|
shadowMode: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loader: 'css-loader',
|
||||||
|
options: {
|
||||||
|
sourceMap: false,
|
||||||
|
importLoaders: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loader: 'postcss-loader',
|
||||||
|
options: {
|
||||||
|
sourceMap: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loader: 'sass-loader',
|
||||||
|
options: {
|
||||||
|
sourceMap: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -71,23 +159,12 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.ContextReplacementPlugin(
|
|
||||||
/moment[\/\\]locale$/,
|
|
||||||
/ru-RU/
|
|
||||||
),
|
|
||||||
new webpack.DefinePlugin({
|
|
||||||
BASE_URL: JSON.stringify(`http://localhost:${ssrPort}`)
|
|
||||||
}),
|
|
||||||
new FriendlyErrorsPlugin(),
|
|
||||||
new VueLoaderPlugin(),
|
new VueLoaderPlugin(),
|
||||||
|
new FriendlyErrorsWebpackPlugin(),
|
||||||
new TSLintPlugin({
|
new DefinePlugin({
|
||||||
files: [
|
NODE_ENV: isProduction ? "'production'" : "'development'"
|
||||||
'./client/**/*.ts',
|
|
||||||
'./server/**/*.ts',
|
|
||||||
'./client/**/*.tsx',
|
|
||||||
'./server/**/*.tsx'
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.exports = config
|
||||||
|
@ -1,69 +1,39 @@
|
|||||||
const path = require('path')
|
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 merge = require('webpack-merge')
|
||||||
const VueClientPlugin = require('vue-server-renderer/client-plugin')
|
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
|
||||||
|
const WebpackBar = require('webpackbar')
|
||||||
|
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')
|
||||||
|
|
||||||
const { appName, clientPort, distPath, env } = require('./build-config')
|
|
||||||
const baseConfig = require('./webpack.base.conf')
|
const baseConfig = require('./webpack.base.conf')
|
||||||
|
|
||||||
const isProduction = process.env.NODE_ENV === 'production'
|
const isProduction = process.env.NODE_ENV === 'production'
|
||||||
|
|
||||||
const webpackConfig = merge(baseConfig, {
|
const webpackConfig = merge(baseConfig, {
|
||||||
entry: {
|
// Entry should be array cause of webpack-hot-client requirements
|
||||||
app: rootResolve('./client/entry.client.ts')
|
mode: isProduction ? 'production' : 'development',
|
||||||
},
|
entry: [path.resolve(__dirname, '../client/entry.client.ts')],
|
||||||
|
devtool: isProduction ? false : 'cheap-module-source-map',
|
||||||
output: {
|
optimization: {
|
||||||
path: distPath,
|
splitChunks: {
|
||||||
filename: `static/js/[name].${isProduction ? '[hash].' : ''}js`
|
name: 'manifest',
|
||||||
|
minChunks: Infinity
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
plugins: [
|
plugins: [
|
||||||
new VueClientPlugin(),
|
new VueSSRClientPlugin(),
|
||||||
|
new WebpackBar({
|
||||||
new webpack.DefinePlugin(Object.assign({}, env, {
|
name: 'Client'
|
||||||
'process.env.VUE_ENV': JSON.stringify('client')
|
|
||||||
})),
|
|
||||||
|
|
||||||
new HtmlWebpackPlugin({
|
|
||||||
template: rootResolve('./index.html'),
|
|
||||||
minify: true,
|
|
||||||
inject: true,
|
|
||||||
title: appName
|
|
||||||
})
|
})
|
||||||
|
// TODO make it work
|
||||||
|
/* ,
|
||||||
|
new ForkTsCheckerWebpackPlugin({
|
||||||
|
vue: true,
|
||||||
|
tslint: true,
|
||||||
|
formatter: 'codeframe',
|
||||||
|
checkSyntacticErrors: false
|
||||||
|
}) */
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
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
|
module.exports = webpackConfig
|
||||||
|
|
||||||
function rootResolve (filePath) {
|
|
||||||
return path.resolve(__dirname, '../', filePath)
|
|
||||||
}
|
|
||||||
|
@ -1,60 +1,37 @@
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
|
||||||
const webpack = require('webpack')
|
|
||||||
const merge = require('webpack-merge')
|
const merge = require('webpack-merge')
|
||||||
const nodeExternals = require('webpack-node-externals')
|
const nodeExternals = require('webpack-node-externals')
|
||||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
|
||||||
const VueServerPlugin = require('vue-server-renderer/server-plugin')
|
const WebpackBar = require('webpackbar')
|
||||||
const axios = require('axios')
|
|
||||||
const { distPath, env, ssrPort } = require('./build-config')
|
|
||||||
const baseConfig = require('./webpack.base.conf')
|
const baseConfig = require('./webpack.base.conf')
|
||||||
|
|
||||||
axios.defaults.baseURL = `http://localhost:${ssrPort}`
|
const isProduction = process.env.NODE_ENV === 'production'
|
||||||
|
|
||||||
const webpackConfig = merge(baseConfig, {
|
const webpackConfig = merge(baseConfig, {
|
||||||
mode: process.env.NODE_ENV,
|
mode: isProduction ? 'production' : 'development',
|
||||||
|
|
||||||
target: 'node',
|
target: 'node',
|
||||||
|
devtool: isProduction ? false : 'source-map',
|
||||||
entry: rootResolve('./client/entry.server.ts'),
|
entry: path.resolve(__dirname, '../client/entry.server.ts'),
|
||||||
|
|
||||||
output: {
|
output: {
|
||||||
path: distPath,
|
filename: 'server-bundle.js',
|
||||||
filename: 'server-build.js',
|
|
||||||
libraryTarget: 'commonjs2'
|
libraryTarget: 'commonjs2'
|
||||||
},
|
},
|
||||||
|
|
||||||
externals: nodeExternals({
|
externals: nodeExternals({
|
||||||
whitelist: [
|
whitelist: [
|
||||||
/\.css$/,
|
/\.css$/,
|
||||||
/\.vue$/,
|
/\.sass$/,
|
||||||
/babel-polyfill/
|
/\.scss$/,
|
||||||
|
/\.svg$/
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
|
|
||||||
plugins: [
|
plugins: [
|
||||||
new VueServerPlugin(),
|
new VueSSRServerPlugin(),
|
||||||
|
new WebpackBar({
|
||||||
new webpack.DefinePlugin(Object.assign({}, env, {
|
name: 'Server',
|
||||||
'process.env.VUE_ENV': JSON.stringify('server')
|
color: 'orange'
|
||||||
}))
|
})
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
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
|
module.exports = webpackConfig
|
||||||
|
|
||||||
function rootResolve (filePath) {
|
|
||||||
return path.resolve(__dirname, '../', filePath)
|
|
||||||
}
|
|
||||||
|
@ -17,10 +17,9 @@ import 'client/assets/code-highlight.css'
|
|||||||
import AppComponent from './App.vue'
|
import AppComponent from './App.vue'
|
||||||
import { createRouter } from './router'
|
import { createRouter } from './router'
|
||||||
import { createStore } from './store'
|
import { createStore } from './store'
|
||||||
|
import config from '../config'
|
||||||
|
|
||||||
// webpack DefinePlugin constant, see build/webpack.base.conf.js for
|
axios.defaults.baseURL = `http://localhost:${config.port}`
|
||||||
declare var BASE_URL: string
|
|
||||||
axios.defaults.baseURL = BASE_URL
|
|
||||||
|
|
||||||
const icons = {}
|
const icons = {}
|
||||||
// TODO import and add only used icons for production
|
// TODO import and add only used icons for production
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import 'reflect-metadata'
|
|
||||||
import '@babel/polyfill'
|
|
||||||
import _get from 'lodash/get'
|
import _get from 'lodash/get'
|
||||||
|
|
||||||
import { createApp } from './app'
|
import { createApp } from './app'
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'reflect-metadata'
|
|
||||||
import _get from 'lodash/get'
|
import _get from 'lodash/get'
|
||||||
import { createApp } from './app'
|
import { createApp } from './app'
|
||||||
|
|
||||||
@ -10,6 +9,7 @@ export default async context => {
|
|||||||
router.onReady(() => {
|
router.onReady(() => {
|
||||||
const matchedComponents = router.getMatchedComponents()
|
const matchedComponents = router.getMatchedComponents()
|
||||||
|
|
||||||
|
// TODO not reject, create fallback to 404 component
|
||||||
if (!matchedComponents.length) {
|
if (!matchedComponents.length) {
|
||||||
return reject({
|
return reject({
|
||||||
code: 404,
|
code: 404,
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
export default class DeferredPromise {
|
|
||||||
constructor () {
|
|
||||||
this._promise = new Promise((resolve, reject) => {
|
|
||||||
// assign the resolve and reject functions to `this`
|
|
||||||
// making them usable on the class instance
|
|
||||||
this.resolve = resolve
|
|
||||||
this.reject = reject
|
|
||||||
})
|
|
||||||
// bind `then` and `catch` to implement the same interface as Promise
|
|
||||||
this.then = this._promise.then.bind(this._promise)
|
|
||||||
this.catch = this._promise.catch.bind(this._promise)
|
|
||||||
this[Symbol.toStringTag] = 'Promise'
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,7 @@
|
|||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import { Mixin } from 'vue-mixin-decorator'
|
import { Mixin } from 'vue-mixin-decorator'
|
||||||
import ConfirmDialog from 'client/components/ConfirmDialog.vue'
|
import ConfirmDialog from 'client/components/ConfirmDialog.vue'
|
||||||
import DeferredPromise from 'client/helpers/DeferredPromise'
|
import DeferredPromise from 'utils/DeferredPromise'
|
||||||
|
|
||||||
const ComponentClass = Vue.extend(ConfirmDialog)
|
const ComponentClass = Vue.extend(ConfirmDialog)
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import { Mixin } from 'vue-mixin-decorator'
|
import { Mixin } from 'vue-mixin-decorator'
|
||||||
import ConflictDialog from 'client/components/ConflictDialog.vue'
|
import ConflictDialog from 'client/components/ConflictDialog.vue'
|
||||||
import DeferredPromise from 'client/helpers/DeferredPromise'
|
import DeferredPromise from 'utils/DeferredPromise'
|
||||||
|
|
||||||
const ComponentClass = Vue.extend(ConflictDialog)
|
const ComponentClass = Vue.extend(ConflictDialog)
|
||||||
|
|
||||||
|
@ -6,10 +6,10 @@ interface ICategoryState {
|
|||||||
category: ICategoryFull
|
category: ICategoryFull
|
||||||
}
|
}
|
||||||
|
|
||||||
const state: ICategoryState = {
|
const state = (): ICategoryState => ({
|
||||||
categoryList: [],
|
categoryList: [],
|
||||||
category: null
|
category: null
|
||||||
}
|
})
|
||||||
|
|
||||||
const getters: GetterTree<ICategoryState, any> = {}
|
const getters: GetterTree<ICategoryState, any> = {}
|
||||||
|
|
||||||
|
@ -11,9 +11,9 @@ interface ICategoryItemState {
|
|||||||
categoryItemList: ICategoryItem[]
|
categoryItemList: ICategoryItem[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const state: ICategoryItemState = {
|
const state = (): ICategoryItemState => ({
|
||||||
categoryItemList: []
|
categoryItemList: []
|
||||||
}
|
})
|
||||||
|
|
||||||
const getters: GetterTree<ICategoryItemState, any> = {}
|
const getters: GetterTree<ICategoryItemState, any> = {}
|
||||||
|
|
||||||
@ -118,7 +118,7 @@ const actions: ActionTree<ICategoryItemState, any> = {
|
|||||||
},
|
},
|
||||||
async updateCategoryDescription (
|
async updateCategoryDescription (
|
||||||
{ dispatch }: ActionContext<ICategoryItemState, any>,
|
{ dispatch }: ActionContext<ICategoryItemState, any>,
|
||||||
{ id, original, modified }: {id: string, original: string, modified: string}
|
{ id, original, modified }: { id: string, original: string, modified: string }
|
||||||
) {
|
) {
|
||||||
const createdDescription = await CategoryItemService.updateCategoryDescription({
|
const createdDescription = await CategoryItemService.updateCategoryDescription({
|
||||||
id,
|
id,
|
||||||
|
@ -6,10 +6,10 @@ interface IWikiState {
|
|||||||
searchInput: string
|
searchInput: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const state: IWikiState = {
|
const state = (): IWikiState => ({
|
||||||
searchResults: [],
|
searchResults: [],
|
||||||
searchInput: ''
|
searchInput: ''
|
||||||
}
|
})
|
||||||
|
|
||||||
const getters: GetterTree<IWikiState, any> = {}
|
const getters: GetterTree<IWikiState, any> = {}
|
||||||
|
|
||||||
|
4
front/config.js
Normal file
4
front/config.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export default {
|
||||||
|
apiUrl: 'https://staging.guide.aelve.com:4400/',
|
||||||
|
port: process.env.PORT || 5000
|
||||||
|
}
|
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"apiUrl": "http://localhost:4400"
|
|
||||||
}
|
|
4
front/index.d.ts
vendored
4
front/index.d.ts
vendored
@ -1,4 +0,0 @@
|
|||||||
declare module '*.vue' {
|
|
||||||
const content: any
|
|
||||||
export default content
|
|
||||||
}
|
|
@ -3,10 +3,12 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
<title> Aelve Guide </title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app">
|
||||||
|
<!--vue-ssr-outlet-->
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
5870
front/package-lock.json
generated
5870
front/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -4,84 +4,82 @@
|
|||||||
"description": "Aelve Guide",
|
"description": "Aelve Guide",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"client:dev": "cross-env NODE_ENV=development webpack-dev-server --config ./build/webpack.client.conf.js",
|
"dev": "cross-env NODE_ENV=development nodemon --watch",
|
||||||
"client:build": "cross-env NODE_ENV=production webpack --config ./build/webpack.client.conf.js",
|
"build": "cross-env NODE_ENV=production rimraf dist && npm run build:src && copyfiles index.html package.json dist/src && copyfiles package.json dist && tsc -p ./prod.tsconfig.json --outDir dist",
|
||||||
"server:dev": "cross-env NODE_ENV=development nodemon ./server/bin.js --watch server/**/*.js",
|
"build:src": "concurrently \"npm run build:client\" \"npm run build:server\"",
|
||||||
"server:build": "cross-env NODE_ENV=production webpack --config ./build/webpack.server.conf.js",
|
"build:client": "cross-env NODE_ENV=production webpack --config build/webpack.client.conf.js --hide-modules",
|
||||||
"build-all": "npm run client:build && npm run server:build",
|
"build:server": "cross-env NODE_ENV=production webpack --config build/webpack.server.conf.js --hide-modules",
|
||||||
"start:dev": "concurrently \"npm run client:dev\" \"npm run server:dev\"",
|
|
||||||
"start:prod": "cross-env NODE_ENV=production node server/bin.js",
|
|
||||||
"test": "testcafe chrome client/tests/*.test.js --app \"npm run start:dev\" --app-init-delay 10000"
|
"test": "testcafe chrome client/tests/*.test.js --app \"npm run start:dev\" --app-init-delay 10000"
|
||||||
},
|
},
|
||||||
|
"nodemonConfig": {
|
||||||
|
"ext": "ts",
|
||||||
|
"exec": "ts-node -O {\\\"module\\\":\\\"commonjs\\\"} ./server.ts",
|
||||||
|
"watch": [
|
||||||
|
"build/**/*.js",
|
||||||
|
"build/**/*.ts",
|
||||||
|
"config.js"
|
||||||
|
],
|
||||||
|
"ignore": [
|
||||||
|
"src/**/*.spec.ts",
|
||||||
|
"src/**/*.d.ts"
|
||||||
|
]
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@junyiz/koa-proxy-pass": "^1.2.1",
|
"@fortawesome/fontawesome-svg-core": "^1.2.17",
|
||||||
|
"@fortawesome/free-solid-svg-icons": "^5.8.1",
|
||||||
|
"@fortawesome/vue-fontawesome": "^0.1.6",
|
||||||
"axios": "^0.18.0",
|
"axios": "^0.18.0",
|
||||||
"easymde": "^2.4.2",
|
"easymde": "^2.5.1",
|
||||||
"koa": "^2.5.0",
|
"koa": "^2.7.0",
|
||||||
"koa-bodyparser": "^4.2.0",
|
"koa-bodyparser": "^4.2.1",
|
||||||
"koa-proxies": "^0.7.0",
|
"koa-mount": "^4.0.0",
|
||||||
"koa-proxy": "^0.9.0",
|
"koa-proxy": "^0.9.0",
|
||||||
"koa-router": "^7.4.0",
|
"koa-static": "^5.0.0",
|
||||||
"koa-static": "^4.0.2",
|
"lodash": "^4.17.11",
|
||||||
"koa-static-server": "^1.3.4",
|
"moment": "^2.24.0",
|
||||||
"lodash": "^4.17.10",
|
"normalize-url": "^4.3.0",
|
||||||
"moment": "^2.22.1",
|
|
||||||
"normalize-url": "^4.1.0",
|
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"reflect-metadata": "^0.1.12",
|
"vue": "^2.6.10",
|
||||||
"vue": "^2.6.6",
|
"vue-class-component": "^7.0.2",
|
||||||
"vue-class-component": "^7.0.1",
|
"vue-mixin-decorator": "^1.1.1",
|
||||||
"vue-mixin-decorator": "^1.0.0",
|
"vue-property-decorator": "^8.1.0",
|
||||||
"vue-property-decorator": "^7.0.0",
|
"vue-router": "^3.0.4",
|
||||||
"vue-router": "^3.0.1",
|
"vue-server-renderer": "^2.6.10",
|
||||||
"vue-server-renderer": "^2.6.6",
|
"vuetify": "^1.5.11",
|
||||||
"vuetify": "^1.5.5",
|
"vuex": "^3.1.0",
|
||||||
"vuex": "^3.0.1",
|
|
||||||
"vuex-router-sync": "^5.0.0"
|
"vuex-router-sync": "^5.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.2.2",
|
"@babel/core": "^7.4.3",
|
||||||
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
"@vue/preload-webpack-plugin": "^1.1.0",
|
||||||
"@babel/plugin-transform-runtime": "^7.2.0",
|
"autoprefixer": "^9.5.1",
|
||||||
"@babel/polyfill": "^7.2.5",
|
|
||||||
"@babel/preset-env": "^7.3.1",
|
|
||||||
"@fortawesome/fontawesome-free": "^5.3.1",
|
|
||||||
"@fortawesome/fontawesome-svg-core": "^1.2.12",
|
|
||||||
"@fortawesome/free-solid-svg-icons": "^5.6.3",
|
|
||||||
"@fortawesome/vue-fontawesome": "^0.1.4",
|
|
||||||
"@types/lodash": "^4.14.116",
|
|
||||||
"babel-loader": "^8.0.5",
|
"babel-loader": "^8.0.5",
|
||||||
"babel-plugin-transform-runtime": "^6.23.0",
|
"concurrently": "^4.1.0",
|
||||||
"babel-preset-env": "^1.7.0",
|
"copyfiles": "^2.1.0",
|
||||||
"babel-preset-stage-2": "^6.24.1",
|
|
||||||
"concurrently": "^3.5.1",
|
|
||||||
"cross-env": "^5.2.0",
|
"cross-env": "^5.2.0",
|
||||||
"css-loader": "^0.28.11",
|
"css-loader": "^2.1.1",
|
||||||
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
"file-loader": "^3.0.1",
|
||||||
"file-loader": "^1.1.11",
|
"fork-ts-checker-webpack-plugin": "^1.0.2",
|
||||||
"friendly-errors-webpack-plugin": "^1.7.0",
|
"friendly-errors-webpack-plugin": "^1.7.0",
|
||||||
"html-webpack-plugin": "^3.2.0",
|
"koa-webpack": "^5.2.2",
|
||||||
"material-design-icons-iconfont": "^3.0.3",
|
|
||||||
"memory-fs": "^0.4.1",
|
"memory-fs": "^0.4.1",
|
||||||
"mini-css-extract-plugin": "^0.4.0",
|
"nodemon": "^1.18.11",
|
||||||
"nodemon": "^1.17.5",
|
"postcss-loader": "^3.0.0",
|
||||||
"postcss-loader": "^2.1.5",
|
"rimraf": "^2.6.3",
|
||||||
"stylus": "^0.54.5",
|
"testcafe": "^1.1.2",
|
||||||
"stylus-loader": "^3.0.2",
|
"testcafe-vue-selectors": "^3.1.0",
|
||||||
"testcafe": "^1.0.1",
|
"ts-loader": "^5.3.3",
|
||||||
"testcafe-vue-selectors": "^3.0.0",
|
"ts-node": "^8.0.3",
|
||||||
"ts-loader": "^4.4.2",
|
"tslint": "^5.15.0",
|
||||||
"tslint": "^5.11.0",
|
"typescript": "^3.4.3",
|
||||||
"tslint-webpack-plugin": "^1.2.2",
|
"url-loader": "^1.1.2",
|
||||||
"typescript": "^2.9.2",
|
"vue-loader": "^15.7.0",
|
||||||
"url-loader": "^1.0.1",
|
|
||||||
"vue-loader": "^15.6.2",
|
|
||||||
"vue-style-loader": "^4.1.2",
|
"vue-style-loader": "^4.1.2",
|
||||||
"vue-template-compiler": "^2.6.6",
|
"vue-template-compiler": "^2.6.10",
|
||||||
"webpack": "^4.29.3",
|
"webpack": "^4.30.0",
|
||||||
"webpack-cli": "^3.2.1",
|
"webpack-cli": "^3.3.0",
|
||||||
"webpack-dev-server": "^3.1.14",
|
"webpack-merge": "^4.2.1",
|
||||||
"webpack-merge": "^4.1.2",
|
"webpack-node-externals": "^1.7.2",
|
||||||
"webpack-node-externals": "^1.7.2"
|
"webpackbar": "^3.1.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// TODO configure
|
||||||
|
|
||||||
const autoprefixer = require('autoprefixer')
|
const autoprefixer = require('autoprefixer')
|
||||||
|
|
||||||
const plugins = [
|
const plugins = [
|
||||||
|
27
front/prod.tsconfig.json
Normal file
27
front/prod.tsconfig.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"./server.ts"
|
||||||
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"allowJs": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "esnext",
|
||||||
|
"strict": false,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"sourceMap": false,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"skipDefaultLibCheck": true,
|
||||||
|
"strictNullChecks": false,
|
||||||
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"es2017",
|
||||||
|
"es5"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
34
front/server.ts
Normal file
34
front/server.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import Koa from 'koa'
|
||||||
|
import bodyparser from 'koa-bodyparser'
|
||||||
|
import proxy from 'koa-proxy'
|
||||||
|
import config from './config.js'
|
||||||
|
|
||||||
|
const { port, apiUrl } = config
|
||||||
|
const isProduction = process.env.NODE_ENV === 'production'
|
||||||
|
|
||||||
|
async function start () {
|
||||||
|
const app = new Koa()
|
||||||
|
|
||||||
|
// TODO replace proxy lib or write own middleware for log and flexibility
|
||||||
|
app.use(proxy({
|
||||||
|
requestOptions: {
|
||||||
|
strictSSL: false
|
||||||
|
},
|
||||||
|
host: apiUrl,
|
||||||
|
match: /^\/api\//,
|
||||||
|
map: (path: string) => path.replace('/api', '')
|
||||||
|
}))
|
||||||
|
app.use(bodyparser())
|
||||||
|
|
||||||
|
const setupServer = isProduction
|
||||||
|
? (await import('./build/setupProdServer')).default
|
||||||
|
: (await import('./build/setupDevServer')).default
|
||||||
|
|
||||||
|
await setupServer(app)
|
||||||
|
|
||||||
|
app.listen(port, () => {
|
||||||
|
console.log(`[Info] Server is on at ${port}.`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
start()
|
@ -1,38 +0,0 @@
|
|||||||
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}.`)
|
|
||||||
})
|
|
@ -1,14 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -1,75 +0,0 @@
|
|||||||
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.url
|
|
||||||
|
|
||||||
// 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'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>{{title}}</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div id="app">
|
|
||||||
<!--vue-ssr-outlet-->
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -13,15 +13,17 @@
|
|||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"strict": true,
|
"strict": false,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"declaration": true,
|
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"skipDefaultLibCheck": true,
|
"skipDefaultLibCheck": true,
|
||||||
"strictNullChecks": false,
|
"strictNullChecks": false,
|
||||||
"lib": [
|
"lib": [
|
||||||
"dom",
|
"dom",
|
||||||
"es2017"
|
"es2017",
|
||||||
]
|
"es5"
|
||||||
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -7,7 +7,7 @@
|
|||||||
"rules": {
|
"rules": {
|
||||||
"arrow-parens": false,
|
"arrow-parens": false,
|
||||||
"comment-format": [true, "check-space"],
|
"comment-format": [true, "check-space"],
|
||||||
"max-classes-per-file": [false],
|
"max-classes-per-file": false,
|
||||||
"member-access": false,
|
"member-access": false,
|
||||||
"member-ordering": false,
|
"member-ordering": false,
|
||||||
"no-console": false,
|
"no-console": false,
|
||||||
|
11
front/types/index.d.ts
vendored
Normal file
11
front/types/index.d.ts
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
declare module '*.vue' {
|
||||||
|
const content: any
|
||||||
|
export default content
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '*/config.js' {
|
||||||
|
const content: any
|
||||||
|
export default content
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'koa-proxy'
|
137
front/utils/DeferredPromise.ts
Normal file
137
front/utils/DeferredPromise.ts
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
// export default class DeferredPromise<T = any> extends Promise<T> {
|
||||||
|
// readonly [Symbol.toStringTag]: 'Promise'
|
||||||
|
// // public resolve: (value?: T | PromiseLike<T>) => void
|
||||||
|
// // public reject: (reason?: any) => void
|
||||||
|
// constructor () {
|
||||||
|
// super((resolve, reject) => {
|
||||||
|
// this.resolve = resolve
|
||||||
|
// this.reject = reject
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// TODO refactor
|
||||||
|
|
||||||
|
export default class DeferredPromise {
|
||||||
|
resolve: any
|
||||||
|
reject: any
|
||||||
|
_promise: any
|
||||||
|
catch: any
|
||||||
|
then: any
|
||||||
|
constructor () {
|
||||||
|
this._promise = new Promise((resolve, reject) => {
|
||||||
|
// assign the resolve and reject functions to `this`
|
||||||
|
// making them usable on the class instance
|
||||||
|
this.resolve = resolve
|
||||||
|
this.reject = reject
|
||||||
|
})
|
||||||
|
// bind `then` and `catch` to implement the same interface as Promise
|
||||||
|
this.then = this._promise.then.bind(this._promise)
|
||||||
|
this.catch = this._promise.catch.bind(this._promise)
|
||||||
|
this[Symbol.toStringTag] = 'Promise'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// export type PromiseStatus = 'resolved' | 'rejected' | 'pending'
|
||||||
|
|
||||||
|
// export default class DeferredPromise<T = any> {
|
||||||
|
|
||||||
|
// static RESOLVED: string = 'resolved'
|
||||||
|
// static REJECTED: string = 'rejected'
|
||||||
|
// static PENDING: string = 'pending'
|
||||||
|
|
||||||
|
// static resolve<T> (value?: T): DeferredPromise<T> {
|
||||||
|
// const promise = new DeferredPromise<T>()
|
||||||
|
// promise.resolve(value)
|
||||||
|
// return promise
|
||||||
|
// }
|
||||||
|
|
||||||
|
// static reject<T> (reason: any): DeferredPromise<T> {
|
||||||
|
// const promise = new DeferredPromise<T>()
|
||||||
|
// promise.reject(reason)
|
||||||
|
// return promise
|
||||||
|
// }
|
||||||
|
|
||||||
|
// private _promise: Promise<T>
|
||||||
|
// private _resolve: ((value: T) => any)
|
||||||
|
// private _reject: ((error: any) => any)
|
||||||
|
// private _status: PromiseStatus
|
||||||
|
|
||||||
|
// constructor (callback?: (deferred: DeferredPromise<T>) => any) {
|
||||||
|
// this._status = DeferredPromise.PENDING as PromiseStatus
|
||||||
|
|
||||||
|
// this._promise = new Promise<T>((resolve: any, reject: any) => {
|
||||||
|
// this._resolve = resolve
|
||||||
|
// this._reject = reject
|
||||||
|
|
||||||
|
// if (callback !== void 0) { callback.call(this, this) }
|
||||||
|
// }).then((value: T) => {
|
||||||
|
// this._status = DeferredPromise.RESOLVED as PromiseStatus
|
||||||
|
// return value
|
||||||
|
// }, (error: any) => {
|
||||||
|
// this._status = DeferredPromise.REJECTED as PromiseStatus
|
||||||
|
// throw error
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// get status (): PromiseStatus {
|
||||||
|
// return this._status
|
||||||
|
// }
|
||||||
|
|
||||||
|
// get promise (): Promise<T> {
|
||||||
|
// return this._promise
|
||||||
|
// }
|
||||||
|
|
||||||
|
// resolve (value?: T): void {
|
||||||
|
// if (this._status === DeferredPromise.PENDING) {
|
||||||
|
// this._resolve(value)
|
||||||
|
// } else {
|
||||||
|
// throw new TypeError('promise already resolved/rejected')
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// reject (reason: any): void {
|
||||||
|
// if (this._status === DeferredPromise.PENDING) {
|
||||||
|
// this._reject(reason)
|
||||||
|
// } else {
|
||||||
|
// throw new TypeError('promise already resolved/rejected')
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// then<A = any> (onFulfilled: ((result: T) => A), onRejected?: ((reason: any) => A)): DeferredPromise<A> {
|
||||||
|
// return new DeferredPromise<A>((deferred: DeferredPromise<A>) => {
|
||||||
|
// this._promise.then((result: T) => {
|
||||||
|
// try {
|
||||||
|
// deferred.resolve(onFulfilled(result))
|
||||||
|
// } catch (error) {
|
||||||
|
// deferred.reject(error)
|
||||||
|
// }
|
||||||
|
// }, (reason: any) => {
|
||||||
|
// if (onRejected === void 0) {
|
||||||
|
// deferred.reject(reason)
|
||||||
|
// } else {
|
||||||
|
// try {
|
||||||
|
// deferred.resolve(onRejected(reason))
|
||||||
|
// } catch (error) {
|
||||||
|
// deferred.reject(error)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// catch<A = any> (onRejected: ((reason: any) => A)): DeferredPromise<A | T> {
|
||||||
|
// return new DeferredPromise<A | T>((deferred: DeferredPromise<A | T>) => {
|
||||||
|
// this._promise.then((result: T) => {
|
||||||
|
// deferred.resolve(result)
|
||||||
|
// }, (reason: any) => {
|
||||||
|
// try {
|
||||||
|
// deferred.resolve(onRejected(reason))
|
||||||
|
// } catch (error) {
|
||||||
|
// deferred.reject(error)
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// }
|
Loading…
Reference in New Issue
Block a user