├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── LICENSE ├── README.md ├── config └── dev.env.js ├── dev ├── build.js ├── dev-client.js ├── local-server.js ├── start.js ├── utils.js ├── webpack.base.config.js ├── webpack.dev.config.js ├── webpack.prod.config.js └── webpack.server.config.js ├── local └── server.js ├── package.json ├── server ├── app.js ├── index.template.html ├── package.json ├── server.js └── yarn.lock ├── serverless.yml ├── src ├── App.vue ├── app-client.js ├── app-server.js ├── app-universal.js ├── index.html ├── layouts │ └── default.vue ├── models │ ├── Memo.js │ └── MemoManager.js ├── pages │ ├── memo.vue │ └── memos.vue ├── parts │ ├── global-footer.vue │ └── global-header.vue ├── router │ ├── index.js │ └── routes.js ├── scss │ └── _variables.scss ├── static │ ├── favicons │ │ ├── android-chrome-144x144.png │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-36x36.png │ │ ├── android-chrome-384x384.png │ │ ├── android-chrome-48x48.png │ │ ├── android-chrome-512x512.png │ │ ├── android-chrome-72x72.png │ │ ├── android-chrome-96x96.png │ │ ├── apple-touch-icon-114x114.png │ │ ├── apple-touch-icon-120x120.png │ │ ├── apple-touch-icon-144x144.png │ │ ├── apple-touch-icon-152x152.png │ │ ├── apple-touch-icon-180x180.png │ │ ├── apple-touch-icon-57x57.png │ │ ├── apple-touch-icon-60x60.png │ │ ├── apple-touch-icon-72x72.png │ │ ├── apple-touch-icon-76x76.png │ │ ├── apple-touch-icon-precomposed.png │ │ ├── apple-touch-icon.png │ │ ├── browserconfig.xml │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon-96x96.png │ │ ├── favicon.ico │ │ ├── mstile-144x144.png │ │ ├── mstile-150x150.png │ │ ├── mstile-310x150.png │ │ ├── mstile-310x310.png │ │ ├── mstile-70x70.png │ │ └── safari-pinned-tab.svg │ └── manifest.json ├── store │ ├── control.js │ ├── index.js │ └── memo.js └── styles.scss └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }] 9 | ], 10 | "plugins": ["transform-runtime"] 11 | } 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/** 2 | dev/** 3 | webpack.* 4 | *config.* -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | parserOptions: { 5 | sourceType: 'module' 6 | }, 7 | env: { 8 | browser: true, 9 | }, 10 | 11 | extends: [ 12 | // 'airbnb-base', 13 | ], 14 | 15 | rules: { 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .serverless 4 | 5 | sample -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 mya-ake 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vuejs-pwa-and-ssr-on-lambda 2 | Vue.jsでPWA作って、LambdaでSSRすることを~~目標とする~~達成したリポジトリ。 3 | 4 | 検証用で作ってるので無用なコメントアウトなど残ってるかもしれないです。 5 | あと、プロダクション用に作っているわけではないのでこのままは使わない方がいいです。 6 | 7 | 8 | ## ~~使う予定~~使ったのもの 9 | 10 | ### Vue.js 11 | 12 | [https://vuejs.org/](https://vuejs.org/) 13 | 14 | Vue Router や Vuex も利用。 15 | 16 | 17 | ### vue-server-renderer 18 | 19 | [https://ssr.vuejs.org/](https://ssr.vuejs.org/) 20 | 21 | 22 | ### Workbox 23 | 24 | [https://workboxjs.org/](https://workboxjs.org/) 25 | 26 | ※プロダクションビルド時のみ利用。 27 | 28 | 29 | ### Serverless Framework 30 | 31 | [https://serverless.com/](https://serverless.com/) 32 | 33 | 34 | ### Material Components Web 35 | [https://material.io/components/web/](https://material.io/components/web/) 36 | 37 | 38 | 39 | ## デプロイについて 40 | 41 | デプロイはServerless Frameworkを使って行っています。 42 | 43 | デプロイのパッケージには画像などは含まれていません。画像は別途手動でS3に上げてCloudFrontにてAPI GatewayとS3に向きを変えています。 44 | 45 | ## DEMO 46 | 47 | 簡単なメモアプリです。 48 | リストがあって、編集ができるというシンプルなものです。 49 | 50 | [https://vuejs-pwa-ssr.mya-ake.org](https://vuejs-pwa-ssr.mya-ake.org) 51 | -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | const dev = Object.freeze({ 2 | NODE_ENV: 'development', 3 | PORT: 4080, 4 | }); 5 | 6 | module.exports = { 7 | dev, 8 | }; 9 | -------------------------------------------------------------------------------- /dev/build.js: -------------------------------------------------------------------------------- 1 | const ora = require('ora') 2 | const webpack = require('webpack'); 3 | 4 | const { myExecSync, removeDistFiles } = require('./utils'); 5 | const webpackConfig = require('./webpack.prod.config'); 6 | 7 | /** settings */ 8 | process.env.NODE_ENV = 'production' 9 | 10 | /** execute */ 11 | const spinner = ora('building for production...') 12 | spinner.start() 13 | 14 | removeDistFiles(); 15 | 16 | webpack(webpackConfig, (err, stats) => { 17 | spinner.stop() 18 | if (err) throw err; 19 | process.stdout.write(stats.toString({ 20 | colors: true, 21 | modules: false, 22 | children: false, 23 | chunks: false, 24 | chunkModules: false 25 | }) + '\n\n') 26 | }); 27 | 28 | // myExecSync('yarn webpack -p'); 29 | -------------------------------------------------------------------------------- /dev/dev-client.js: -------------------------------------------------------------------------------- 1 | require('eventsource-polyfill'); 2 | var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true'); 3 | 4 | hotClient.subscribe((event) => { 5 | if (event.action === 'reload') { 6 | window.location.reload(); 7 | } 8 | }); 9 | -------------------------------------------------------------------------------- /dev/local-server.js: -------------------------------------------------------------------------------- 1 | const { app } = require('./../server/server'); 2 | 3 | 4 | const port = 4080; 5 | const server = app.listen(port, () => { 6 | console.log(`listening: http://localhost:${port}`); 7 | }); 8 | 9 | module.exports = { 10 | close: () => { 11 | server.close(); 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /dev/start.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const webpackDevMiddleware = require('webpack-dev-middleware'); 3 | const webpackHotMiddleware = require('webpack-hot-middleware'); 4 | const connectHistoryApiFallback = require('connect-history-api-fallback'); 5 | const express = require('express'); 6 | const path = require('path'); 7 | 8 | const config = require('./../config/dev.env'); 9 | const webpackConfig = require('./webpack.dev.config'); 10 | const { myExecSync, removeDistFiles } = require('./utils'); 11 | 12 | /** settings */ 13 | if (!process.env.NODE_ENV) { 14 | process.env.NODE_ENV = config.dev.NODE_ENV; 15 | } 16 | const port = config.dev.PORT; 17 | 18 | // add hot-reload related code to entry chunks 19 | Object.keys(webpackConfig.entry).forEach((name) => { 20 | const entry = path.resolve(__dirname, './dev-client'); 21 | webpackConfig.entry[name] = [entry].concat(webpackConfig.entry[name]) 22 | }) 23 | 24 | const app = express(); 25 | const compiler = webpack(webpackConfig) 26 | 27 | const devMiddleware = webpackDevMiddleware(compiler, { 28 | publicPath: webpackConfig.output.publicPath, 29 | quiet: true 30 | }); 31 | 32 | const hotMiddleware = webpackHotMiddleware(compiler, { 33 | log: false, 34 | heartbeat: 2000 35 | }); 36 | 37 | compiler.plugin('compilation', (compilation) => { 38 | compilation.plugin('html-webpack-plugin-after-emit', (data, callback) => { 39 | hotMiddleware.publish({ action: 'reload' }); 40 | callback(); 41 | }); 42 | }); 43 | 44 | app.use(connectHistoryApiFallback()); 45 | app.use(devMiddleware); 46 | app.use(hotMiddleware); 47 | 48 | 49 | /** execute */ 50 | removeDistFiles(); 51 | 52 | var uri = 'http://localhost:' + port 53 | 54 | var _resolve 55 | var readyPromise = new Promise(resolve => { 56 | _resolve = resolve 57 | }) 58 | 59 | console.log('> Starting dev server...') 60 | devMiddleware.waitUntilValid(() => { 61 | console.log('> Listening at ' + uri + '\n') 62 | _resolve() 63 | }) 64 | 65 | const server = app.listen(port, () => { 66 | console.log(`listening: http://localhost:${port}`); 67 | }); 68 | 69 | module.exports = { 70 | ready: readyPromise, 71 | close: () => { 72 | server.close(); 73 | }, 74 | }; 75 | -------------------------------------------------------------------------------- /dev/utils.js: -------------------------------------------------------------------------------- 1 | const del = require('del'); 2 | const execSync = require('child_process').execSync; 3 | 4 | const myExecSync = (command) => { 5 | execSync(command, 6 | { 7 | stdio: 'inherit', 8 | }); 9 | }; 10 | 11 | const removeDistFiles = () => { 12 | del.sync(['dist/**', '!dist']); 13 | }; 14 | 15 | module.exports = { 16 | myExecSync, 17 | removeDistFiles, 18 | }; 19 | -------------------------------------------------------------------------------- /dev/webpack.base.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 3 | 4 | const resolve = (dir) => { 5 | return path.join(__dirname, '..', dir) 6 | }; 7 | 8 | const isProduction = process.env.NODE_ENV === 'production'; 9 | 10 | const config = { 11 | output: { 12 | path: path.join(__dirname, '..', 'dist', 'assets'), 13 | filename: '[name].[hash].js', 14 | publicPath: '/assets/', 15 | }, 16 | resolve: { 17 | extensions: ['.js', '.vue', '.json'], 18 | alias: { 19 | '~': resolve('src'), 20 | 'vue$': 'vue/dist/vue.runtime.esm.js', 21 | 'vuex$': 'vuex/dist/vuex.esm.js', 22 | }, 23 | }, 24 | module: { 25 | rules: [ 26 | { 27 | test: /\.js$/, 28 | include: [ 29 | resolve('src'), 30 | resolve('node_modules/@material'), 31 | ], 32 | use: ['babel-loader'], 33 | }, 34 | { 35 | test: /\.vue$/, 36 | loader: 'vue-loader', 37 | options: { 38 | extractCSS: isProduction, 39 | postcss: [ 40 | require('autoprefixer')({ 41 | browsers: ['IE 9', 'IE 10', 'IE 11', 'last 2 versions'], 42 | }), 43 | ], 44 | }, 45 | }, 46 | ], 47 | }, 48 | plugins: [ 49 | new CopyWebpackPlugin([ 50 | { 51 | from: resolve('src/static'), 52 | to: resolve('dist'), 53 | }, 54 | ]), 55 | ], 56 | }; 57 | 58 | module.exports = config; 59 | -------------------------------------------------------------------------------- /dev/webpack.dev.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const merge = require('webpack-merge'); 3 | const path = require('path'); 4 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 5 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin'); 6 | const VueSSRClientPlugin = require('vue-server-renderer/client-plugin'); 7 | 8 | const baseConfig = require('./webpack.base.config'); 9 | 10 | const resolve = (dir) => { 11 | return path.join(__dirname, '..', dir) 12 | }; 13 | 14 | const config = merge(baseConfig, { 15 | entry: { 16 | app: path.resolve(__dirname, '../src/app-client.js'), 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.css$/, 22 | use: ['style-loader', 'css-loader'] 23 | }, 24 | { 25 | test: /\.scss$/, 26 | use: [ 27 | 'style-loader', 28 | 'css-loader', 29 | { 30 | loader: 'sass-loader', 31 | options: { 32 | includePaths: [ 33 | resolve('src/scss'), 34 | resolve('node_modules'), 35 | ] 36 | }, 37 | } 38 | ], 39 | }, 40 | ], 41 | }, 42 | plugins: [ 43 | new HtmlWebpackPlugin({ 44 | filename: 'index.html', 45 | template: path.join('src', 'index.html'), 46 | inject: true, 47 | }), 48 | new webpack.HotModuleReplacementPlugin(), 49 | new FriendlyErrorsPlugin(), 50 | new webpack.optimize.CommonsChunkPlugin({ 51 | name: "manifest", 52 | minChunks: Infinity 53 | }), 54 | new VueSSRClientPlugin() 55 | ], 56 | devtool: '#cheap-eval-source-map', 57 | }); 58 | 59 | module.exports = config; 60 | -------------------------------------------------------------------------------- /dev/webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const merge = require('webpack-merge'); 3 | const path = require('path'); 4 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 5 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 6 | const VueSSRClientPlugin = require('vue-server-renderer/client-plugin'); 7 | const workboxPlugin = require('workbox-webpack-plugin'); 8 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 9 | 10 | const baseConfig = require('./webpack.base.config'); 11 | 12 | const env = 'production'; 13 | 14 | const resolve = (dir) => { 15 | return path.join(__dirname, '..', dir) 16 | }; 17 | 18 | const config = merge(baseConfig, { 19 | entry: { 20 | app: path.resolve(__dirname, '../src/app-client.js'), 21 | }, 22 | output: { 23 | filename: '[name].[chunkhash].js', 24 | // publicPath: '/v1/', // リソースに階層が必要な場合設定する 25 | }, 26 | module: { 27 | rules: [ 28 | { 29 | test: /\.css$/, 30 | use: ExtractTextPlugin.extract({ 31 | fallback: 'vue-style-loader', 32 | use: ['css-loader'] 33 | }), 34 | }, 35 | { 36 | test: /\.scss$/, 37 | use: ExtractTextPlugin.extract({ 38 | fallback: 'vue-style-loader', 39 | use: [ 40 | 'css-loader', 41 | { 42 | loader: 'sass-loader', 43 | options: { 44 | includePaths: [ 45 | resolve('src/scss'), 46 | resolve('node_modules'), 47 | ] 48 | }, 49 | } 50 | ] 51 | }), 52 | }, 53 | ], 54 | }, 55 | plugins: [ 56 | new ExtractTextPlugin({ 57 | filename: 'styles.[contenthash].css', 58 | }), 59 | new HtmlWebpackPlugin({ 60 | filename: path.join('..', 'index.html'), 61 | template: path.join('src', 'index.html'), 62 | inject: true, 63 | minify: { 64 | removeComments: true, 65 | collapseWhitespace: true, 66 | }, 67 | }), 68 | new HtmlWebpackPlugin({ 69 | // filename: path.join('..', '..', 'server', 'index.template.html'), 70 | filename: resolve('server/index.template.html'), 71 | template: path.join('src', 'index.html'), 72 | inject: false, 73 | minify: { 74 | collapseWhitespace: true, 75 | }, 76 | }), 77 | new webpack.DefinePlugin({ 78 | 'process.env': { 79 | 'NODE_ENV': '"production"', 80 | 'VUE_ENV': '"client"', 81 | }, 82 | }), 83 | new webpack.optimize.UglifyJsPlugin({ 84 | compress: { 85 | warnings: false 86 | }, 87 | sourceMap: true 88 | }), 89 | new webpack.optimize.CommonsChunkPlugin({ 90 | name: "manifest", 91 | }), 92 | new VueSSRClientPlugin(), 93 | new workboxPlugin({ 94 | globDirectory: 'dist', 95 | globPatterns: ['**/*.{html,js,css}', 'favicons/**/*.{png,ico,xml,svg}'], 96 | swDest: path.join('dist', 'sw.js'), 97 | }), 98 | new CopyWebpackPlugin([ 99 | { 100 | from: resolve('src/static'), 101 | to: resolve('dist'), 102 | }, 103 | ]), 104 | ], 105 | devtool: '#hidden-source-map', 106 | }); 107 | 108 | module.exports = config; 109 | -------------------------------------------------------------------------------- /dev/webpack.server.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const merge = require('webpack-merge'); 3 | const nodeExternals = require('webpack-node-externals'); 4 | const path = require('path'); 5 | const VueSSRServerPlugin = require('vue-server-renderer/server-plugin'); 6 | 7 | 8 | const baseConfig = require('./webpack.base.config'); 9 | 10 | const config = merge(baseConfig, { 11 | entry: { 12 | server: path.resolve(__dirname, '../src/app-server.js'), 13 | }, 14 | target: 'node', 15 | output: { 16 | libraryTarget: 'commonjs2' 17 | }, 18 | // externals: nodeExternals({ 19 | // whitelist: [ 20 | // /\.css$/, 21 | // /\.scss$/, 22 | // /\.vue$/, 23 | // /\.js$/, 24 | // ], 25 | // }), 26 | plugins: [ 27 | new webpack.DefinePlugin({ 28 | 'process.env': { 29 | 'NODE_ENV': '"production"', 30 | 'VUE_ENV': '"server"', 31 | }, 32 | }), 33 | new VueSSRServerPlugin() 34 | ], 35 | devtool: '#source-map', 36 | }); 37 | 38 | module.exports = config; 39 | -------------------------------------------------------------------------------- /local/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | 3 | const appServer = express(); 4 | appServer.get('*', (req, res, next) => { 5 | const fileName = req.originalUrl; 6 | console.log(fileName); 7 | const root = fileName.startsWith('/node_modules/') ? '.' : 'dist'; 8 | res.sendFile(fileName, { root: root }, (err) => { 9 | if (err) { 10 | next(err); 11 | } 12 | }); 13 | }); 14 | 15 | const port = 4080; 16 | const server = appServer.listen(port, () => { 17 | console.log(`listening: http://localhost:${port}`); 18 | }); 19 | 20 | module.exports = { 21 | close: () => { 22 | server.close(); 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vuejs-pwa-and-ssr-on-lambda", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "repository": "https://github.com/mya-ake/vuejs-pwa-and-ssr-on-lambda.git", 6 | "author": "t.shibuta ", 7 | "license": "MIT", 8 | "scripts": { 9 | "build": "node dev/build.js && npm run build:lambda", 10 | "build:dev": "webpack --config ./dev/webpack.dev.config.js", 11 | "build:lambda": "webpack --config ./dev/webpack.server.config.js", 12 | "dev": "node dev/start.js", 13 | "start": "node dev/local-server.js", 14 | "deploy:lambda": "sls deploy", 15 | "remove:lambda": "sls remove" 16 | }, 17 | "dependencies": { 18 | "@material/base": "^0.2.3", 19 | "@material/button": "^0.3.11", 20 | "@material/elevation": "^0.1.11", 21 | "@material/fab": "^0.3.13", 22 | "@material/form-field": "^0.2.11", 23 | "@material/list": "^0.2.14", 24 | "@material/ripple": "^0.8.2", 25 | "@material/snackbar": "^0.3.2", 26 | "@material/textfield": "^0.3.3", 27 | "@material/theme": "^0.1.7", 28 | "@material/toolbar": "^0.4.4", 29 | "@material/typography": "^0.3.0", 30 | "express": "^4.15.4", 31 | "material-components-web": "^0.17.0", 32 | "normalize.css": "^7.0.0", 33 | "vue": "^2.4.2", 34 | "vue-router": "^2.7.0", 35 | "vue-server-renderer": "^2.4.2", 36 | "vuex": "^2.3.1", 37 | "vuex-router-sync": "^4.2.0" 38 | }, 39 | "devDependencies": { 40 | "autoprefixer": "^7.1.2", 41 | "babel-core": "^6.26.0", 42 | "babel-eslint": "^7.2.3", 43 | "babel-loader": "^7.1.2", 44 | "babel-plugin-transform-runtime": "^6.23.0", 45 | "babel-preset-env": "^1.6.0", 46 | "connect-history-api-fallback": "^1.3.0", 47 | "copy-webpack-plugin": "^4.0.1", 48 | "css-loader": "^0.28.5", 49 | "del": "^3.0.0", 50 | "eslint": "^4.5.0", 51 | "eslint-config-airbnb-base": "^11.3.1", 52 | "eslint-plugin-import": "^2.7.0", 53 | "eventsource-polyfill": "^0.9.6", 54 | "extract-text-webpack-plugin": "^3.0.0", 55 | "friendly-errors-webpack-plugin": "^1.6.1", 56 | "html-webpack-plugin": "^2.30.1", 57 | "node-sass": "^4.5.3", 58 | "ora": "^1.3.0", 59 | "postcss": "^6.0.9", 60 | "postcss-loader": "^2.0.6", 61 | "sass-loader": "^6.0.6", 62 | "serverless": "^1.20.2", 63 | "style-loader": "^0.18.2", 64 | "vue-loader": "^13.0.4", 65 | "vue-style-loader": "^3.0.1", 66 | "vue-template-compiler": "^2.4.2", 67 | "webpack": "^3.5.5", 68 | "webpack-dev-middleware": "^1.12.0", 69 | "webpack-hot-middleware": "^2.18.2", 70 | "webpack-merge": "^4.1.0", 71 | "webpack-node-externals": "^1.6.0", 72 | "workbox-webpack-plugin": "^1.3.0" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const awsServerlessExpress = require('aws-serverless-express'); 4 | const awsServerlessExpressMiddleware = require('aws-serverless-express/middleware'); 5 | 6 | const { app } = require('./server'); 7 | app.use(awsServerlessExpressMiddleware.eventContext()); 8 | 9 | const server = awsServerlessExpress.createServer(app); 10 | 11 | module.exports.render = (event, context, callback) => { 12 | console.log(event); 13 | awsServerlessExpress.proxy(server, event, context); 14 | }; 15 | -------------------------------------------------------------------------------- /server/index.template.html: -------------------------------------------------------------------------------- 1 | Vue.js Memos
-------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vuejs-pwa-and-ssr-on-lambda-server", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "dependencies": { 7 | "aws-serverless-express": "^3.0.2", 8 | "express": "^4.15.4", 9 | "vue-server-renderer": "^2.4.2" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const express = require('express'); 5 | const { createBundleRenderer } = require('vue-server-renderer'); 6 | const serverBundle = require('./../dist/assets/vue-ssr-server-bundle.json'); 7 | const template = require('fs').readFileSync('./server/index.template.html', 'utf-8'); 8 | const clientManifest = require('./../dist/assets/vue-ssr-client-manifest.json'); 9 | 10 | const renderer = createBundleRenderer(serverBundle, { 11 | runInNewContext: false, 12 | template, 13 | clientManifest, 14 | }); 15 | 16 | const app = express(); 17 | app.use((req, res, next) => { 18 | res.removeHeader('x-powered-by'); 19 | res.header('no-cache', 'Set-Cookie'); 20 | res.header('x-xss-protection', '1; mode=block'); 21 | res.header('x-frame-options', 'DENY'); 22 | res.header('x-content-type-options', 'nosniff'); 23 | next(); 24 | }); 25 | 26 | 27 | app.use('/favicons/', express.static('dist/favicons', { 28 | index: false, 29 | setHeaders: (res, path, stat) => { 30 | res.header('Cache-Control', 'max-age=86400'); 31 | }, 32 | })); 33 | 34 | app.use('/assets/', express.static('dist/assets', { 35 | index: false, 36 | setHeaders: (res, path, stat) => { 37 | res.header('Cache-Control', 'max-age=60'); 38 | }, 39 | })); 40 | 41 | app.get(['/*.js', '/*.js.map'], (req, res, next) => { 42 | const fileName = req.originalUrl; 43 | const root = 'dist'; 44 | console.log(`static: ${fileName}`); 45 | res.header('Cache-Control', 'no-store, no-cache, max-age=0'); 46 | res.sendFile(fileName, { root: root }, (err) => { 47 | if (err) { 48 | next(err); 49 | } 50 | }); 51 | }); 52 | 53 | app.get(['/*.js', '/*.js.map', '/manifest.json'], (req, res, next) => { 54 | const fileName = req.originalUrl; 55 | const root = 'dist'; 56 | console.log(`static: ${fileName}`); 57 | res.header('Cache-Control', 'max-age=60'); 58 | res.sendFile(fileName, { root: root }, (err) => { 59 | if (err) { 60 | next(err); 61 | } 62 | }); 63 | }); 64 | 65 | app.get('*', (req, res) => { 66 | res.header('content-type', 'text/html'); 67 | res.header('Cache-Control', 'max-age=60'); 68 | const context = { url: req.url }; 69 | console.log(`html: ${req.url}`); 70 | 71 | renderer.renderToString(context, (err, html) => { 72 | if (err) { 73 | console.log(err); 74 | if (err.code === 404) { 75 | res.status(404).end('Page not found') 76 | } else { 77 | res.status(500).end('Internal Server Error') 78 | } 79 | } else { 80 | res.end(html) 81 | } 82 | }); 83 | }); 84 | 85 | module.exports = { 86 | app, 87 | }; 88 | -------------------------------------------------------------------------------- /server/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | accepts@~1.3.3: 6 | version "1.3.3" 7 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.3.tgz#c3ca7434938648c3e0d9c1e328dd68b622c284ca" 8 | dependencies: 9 | mime-types "~2.1.11" 10 | negotiator "0.6.1" 11 | 12 | ansi-regex@^2.0.0: 13 | version "2.1.1" 14 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" 15 | 16 | ansi-styles@^2.2.1: 17 | version "2.2.1" 18 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" 19 | 20 | array-flatten@1.1.1: 21 | version "1.1.1" 22 | resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" 23 | 24 | aws-serverless-express@^3.0.2: 25 | version "3.0.2" 26 | resolved "https://registry.yarnpkg.com/aws-serverless-express/-/aws-serverless-express-3.0.2.tgz#38ceb2a5786aa5cfb51033d9c3692fd7972605db" 27 | dependencies: 28 | binary-case "^1.0.0" 29 | 30 | binary-case@^1.0.0: 31 | version "1.1.3" 32 | resolved "https://registry.yarnpkg.com/binary-case/-/binary-case-1.1.3.tgz#f91d0e3008f1fb0f4043d6594522d66d6d1c8409" 33 | 34 | chalk@^1.1.3: 35 | version "1.1.3" 36 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" 37 | dependencies: 38 | ansi-styles "^2.2.1" 39 | escape-string-regexp "^1.0.2" 40 | has-ansi "^2.0.0" 41 | strip-ansi "^3.0.0" 42 | supports-color "^2.0.0" 43 | 44 | content-disposition@0.5.2: 45 | version "0.5.2" 46 | resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" 47 | 48 | content-type@~1.0.2: 49 | version "1.0.2" 50 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.2.tgz#b7d113aee7a8dd27bd21133c4dc2529df1721eed" 51 | 52 | cookie-signature@1.0.6: 53 | version "1.0.6" 54 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" 55 | 56 | cookie@0.3.1: 57 | version "0.3.1" 58 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" 59 | 60 | debug@2.6.8: 61 | version "2.6.8" 62 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" 63 | dependencies: 64 | ms "2.0.0" 65 | 66 | depd@1.1.1, depd@~1.1.1: 67 | version "1.1.1" 68 | resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" 69 | 70 | destroy@~1.0.4: 71 | version "1.0.4" 72 | resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" 73 | 74 | ee-first@1.1.1: 75 | version "1.1.1" 76 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 77 | 78 | encodeurl@~1.0.1: 79 | version "1.0.1" 80 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20" 81 | 82 | escape-html@~1.0.3: 83 | version "1.0.3" 84 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" 85 | 86 | escape-string-regexp@^1.0.2: 87 | version "1.0.5" 88 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 89 | 90 | etag@~1.8.0: 91 | version "1.8.0" 92 | resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.0.tgz#6f631aef336d6c46362b51764044ce216be3c051" 93 | 94 | express@^4.15.4: 95 | version "4.15.4" 96 | resolved "https://registry.yarnpkg.com/express/-/express-4.15.4.tgz#032e2253489cf8fce02666beca3d11ed7a2daed1" 97 | dependencies: 98 | accepts "~1.3.3" 99 | array-flatten "1.1.1" 100 | content-disposition "0.5.2" 101 | content-type "~1.0.2" 102 | cookie "0.3.1" 103 | cookie-signature "1.0.6" 104 | debug "2.6.8" 105 | depd "~1.1.1" 106 | encodeurl "~1.0.1" 107 | escape-html "~1.0.3" 108 | etag "~1.8.0" 109 | finalhandler "~1.0.4" 110 | fresh "0.5.0" 111 | merge-descriptors "1.0.1" 112 | methods "~1.1.2" 113 | on-finished "~2.3.0" 114 | parseurl "~1.3.1" 115 | path-to-regexp "0.1.7" 116 | proxy-addr "~1.1.5" 117 | qs "6.5.0" 118 | range-parser "~1.2.0" 119 | send "0.15.4" 120 | serve-static "1.12.4" 121 | setprototypeof "1.0.3" 122 | statuses "~1.3.1" 123 | type-is "~1.6.15" 124 | utils-merge "1.0.0" 125 | vary "~1.1.1" 126 | 127 | finalhandler@~1.0.4: 128 | version "1.0.4" 129 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.0.4.tgz#18574f2e7c4b98b8ae3b230c21f201f31bdb3fb7" 130 | dependencies: 131 | debug "2.6.8" 132 | encodeurl "~1.0.1" 133 | escape-html "~1.0.3" 134 | on-finished "~2.3.0" 135 | parseurl "~1.3.1" 136 | statuses "~1.3.1" 137 | unpipe "~1.0.0" 138 | 139 | forwarded@~0.1.0: 140 | version "0.1.0" 141 | resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.0.tgz#19ef9874c4ae1c297bcf078fde63a09b66a84363" 142 | 143 | fresh@0.5.0: 144 | version "0.5.0" 145 | resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.0.tgz#f474ca5e6a9246d6fd8e0953cfa9b9c805afa78e" 146 | 147 | has-ansi@^2.0.0: 148 | version "2.0.0" 149 | resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" 150 | dependencies: 151 | ansi-regex "^2.0.0" 152 | 153 | hash-sum@^1.0.2: 154 | version "1.0.2" 155 | resolved "https://registry.yarnpkg.com/hash-sum/-/hash-sum-1.0.2.tgz#33b40777754c6432573c120cc3808bbd10d47f04" 156 | 157 | he@^1.1.0: 158 | version "1.1.1" 159 | resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" 160 | 161 | http-errors@~1.6.2: 162 | version "1.6.2" 163 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" 164 | dependencies: 165 | depd "1.1.1" 166 | inherits "2.0.3" 167 | setprototypeof "1.0.3" 168 | statuses ">= 1.3.1 < 2" 169 | 170 | inherits@2.0.3: 171 | version "2.0.3" 172 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 173 | 174 | ipaddr.js@1.4.0: 175 | version "1.4.0" 176 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.4.0.tgz#296aca878a821816e5b85d0a285a99bcff4582f0" 177 | 178 | lodash._reinterpolate@~3.0.0: 179 | version "3.0.0" 180 | resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" 181 | 182 | lodash.template@^4.4.0: 183 | version "4.4.0" 184 | resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.4.0.tgz#e73a0385c8355591746e020b99679c690e68fba0" 185 | dependencies: 186 | lodash._reinterpolate "~3.0.0" 187 | lodash.templatesettings "^4.0.0" 188 | 189 | lodash.templatesettings@^4.0.0: 190 | version "4.1.0" 191 | resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.1.0.tgz#2b4d4e95ba440d915ff08bc899e4553666713316" 192 | dependencies: 193 | lodash._reinterpolate "~3.0.0" 194 | 195 | lodash.uniq@^4.5.0: 196 | version "4.5.0" 197 | resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" 198 | 199 | media-typer@0.3.0: 200 | version "0.3.0" 201 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 202 | 203 | merge-descriptors@1.0.1: 204 | version "1.0.1" 205 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" 206 | 207 | methods@~1.1.2: 208 | version "1.1.2" 209 | resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" 210 | 211 | mime-db@~1.29.0: 212 | version "1.29.0" 213 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.29.0.tgz#48d26d235589651704ac5916ca06001914266878" 214 | 215 | mime-types@~2.1.11, mime-types@~2.1.15: 216 | version "2.1.16" 217 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.16.tgz#2b858a52e5ecd516db897ac2be87487830698e23" 218 | dependencies: 219 | mime-db "~1.29.0" 220 | 221 | mime@1.3.4: 222 | version "1.3.4" 223 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" 224 | 225 | ms@2.0.0: 226 | version "2.0.0" 227 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 228 | 229 | negotiator@0.6.1: 230 | version "0.6.1" 231 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" 232 | 233 | on-finished@~2.3.0: 234 | version "2.3.0" 235 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" 236 | dependencies: 237 | ee-first "1.1.1" 238 | 239 | parseurl@~1.3.1: 240 | version "1.3.1" 241 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.1.tgz#c8ab8c9223ba34888aa64a297b28853bec18da56" 242 | 243 | path-parse@^1.0.5: 244 | version "1.0.5" 245 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" 246 | 247 | path-to-regexp@0.1.7: 248 | version "0.1.7" 249 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" 250 | 251 | proxy-addr@~1.1.5: 252 | version "1.1.5" 253 | resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.5.tgz#71c0ee3b102de3f202f3b64f608d173fcba1a918" 254 | dependencies: 255 | forwarded "~0.1.0" 256 | ipaddr.js "1.4.0" 257 | 258 | qs@6.5.0: 259 | version "6.5.0" 260 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.0.tgz#8d04954d364def3efc55b5a0793e1e2c8b1e6e49" 261 | 262 | range-parser@~1.2.0: 263 | version "1.2.0" 264 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" 265 | 266 | resolve@^1.2.0: 267 | version "1.4.0" 268 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.4.0.tgz#a75be01c53da25d934a98ebd0e4c4a7312f92a86" 269 | dependencies: 270 | path-parse "^1.0.5" 271 | 272 | send@0.15.4: 273 | version "0.15.4" 274 | resolved "https://registry.yarnpkg.com/send/-/send-0.15.4.tgz#985faa3e284b0273c793364a35c6737bd93905b9" 275 | dependencies: 276 | debug "2.6.8" 277 | depd "~1.1.1" 278 | destroy "~1.0.4" 279 | encodeurl "~1.0.1" 280 | escape-html "~1.0.3" 281 | etag "~1.8.0" 282 | fresh "0.5.0" 283 | http-errors "~1.6.2" 284 | mime "1.3.4" 285 | ms "2.0.0" 286 | on-finished "~2.3.0" 287 | range-parser "~1.2.0" 288 | statuses "~1.3.1" 289 | 290 | serialize-javascript@^1.3.0: 291 | version "1.4.0" 292 | resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.4.0.tgz#7c958514db6ac2443a8abc062dc9f7886a7f6005" 293 | 294 | serve-static@1.12.4: 295 | version "1.12.4" 296 | resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.12.4.tgz#9b6aa98eeb7253c4eedc4c1f6fdbca609901a961" 297 | dependencies: 298 | encodeurl "~1.0.1" 299 | escape-html "~1.0.3" 300 | parseurl "~1.3.1" 301 | send "0.15.4" 302 | 303 | setprototypeof@1.0.3: 304 | version "1.0.3" 305 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" 306 | 307 | source-map@0.5.6: 308 | version "0.5.6" 309 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" 310 | 311 | "statuses@>= 1.3.1 < 2", statuses@~1.3.1: 312 | version "1.3.1" 313 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" 314 | 315 | strip-ansi@^3.0.0: 316 | version "3.0.1" 317 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" 318 | dependencies: 319 | ansi-regex "^2.0.0" 320 | 321 | supports-color@^2.0.0: 322 | version "2.0.0" 323 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" 324 | 325 | type-is@~1.6.15: 326 | version "1.6.15" 327 | resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" 328 | dependencies: 329 | media-typer "0.3.0" 330 | mime-types "~2.1.15" 331 | 332 | unpipe@~1.0.0: 333 | version "1.0.0" 334 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" 335 | 336 | utils-merge@1.0.0: 337 | version "1.0.0" 338 | resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8" 339 | 340 | vary@~1.1.1: 341 | version "1.1.1" 342 | resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.1.tgz#67535ebb694c1d52257457984665323f587e8d37" 343 | 344 | vue-server-renderer@^2.4.2: 345 | version "2.4.2" 346 | resolved "https://registry.yarnpkg.com/vue-server-renderer/-/vue-server-renderer-2.4.2.tgz#0ba0f984181ea1c455362b09bddf60bc0e0a03fa" 347 | dependencies: 348 | chalk "^1.1.3" 349 | hash-sum "^1.0.2" 350 | he "^1.1.0" 351 | lodash.template "^4.4.0" 352 | lodash.uniq "^4.5.0" 353 | resolve "^1.2.0" 354 | serialize-javascript "^1.3.0" 355 | source-map "0.5.6" 356 | -------------------------------------------------------------------------------- /serverless.yml: -------------------------------------------------------------------------------- 1 | service: 2 | name: ${self:custom.name} 3 | 4 | frameworkVersion: ">=1.19.0" 5 | 6 | provider: 7 | name: aws 8 | runtime: nodejs6.10 9 | region: ap-northeast-1 10 | stage: ${self:custom.stage} 11 | profile: my 12 | apiKeys: 13 | - ${self:custom.name}-${self:custom.stage} 14 | 15 | custom: 16 | name: vuejs-pwa-and-ssr-on-lambda 17 | stage: v1 18 | 19 | package: 20 | include: 21 | - server/**/**.js 22 | - server/**/**.json 23 | - server/index.template.html 24 | - dist/** 25 | - '!dist/favicons/**' 26 | exclude: 27 | - node_modules/** 28 | - src/** 29 | - dev/** 30 | - server/** 31 | - local/** 32 | - config/** 33 | - .** 34 | - LICENSE 35 | - README.md 36 | - package.json 37 | - yarn.lock 38 | - webpack.**.js 39 | 40 | 41 | functions: 42 | render: 43 | handler: server/app.render 44 | timeout: 120 45 | memorySize: 128 46 | events: 47 | - http: 48 | path: '/' 49 | method: get 50 | private: true 51 | - http: 52 | path: '{proxy+}' 53 | method: get 54 | private: true 55 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 20 | 21 | 71 | -------------------------------------------------------------------------------- /src/app-client.js: -------------------------------------------------------------------------------- 1 | import 'normalize.css/normalize.css'; 2 | import './styles.scss'; 3 | 4 | import { createApp } from './app-universal'; 5 | 6 | 7 | const { app, router, store } = createApp(); 8 | 9 | if (window.__INITIAL_STATE__) { 10 | store.replaceState(window.__INITIAL_STATE__) 11 | } 12 | 13 | router.onReady(() => { 14 | app.$mount('#app-root'); 15 | }); 16 | 17 | if (window && 'serviceWorker' in navigator) { 18 | navigator.serviceWorker.register('/sw.js') 19 | .then((registration) => { 20 | console.log(registration); 21 | if (registration.installing) { 22 | console.info('service worker installing'); 23 | } else if (registration.waiting) { 24 | console.info('service worker waiting'); 25 | } else if (registration.active) { 26 | console.info('service worker active'); 27 | } 28 | return registration; 29 | }) 30 | .then((registration) => { 31 | registration.addEventListener('updatefound', (evt) => { 32 | console.info('service worker update found', evt); 33 | }, false); 34 | return registration; 35 | }) 36 | .then((registration) => { 37 | registration.update(); 38 | console.info('service worker update checked'); 39 | return registration; 40 | }) 41 | .catch((err) => { 42 | console.error('service worker install failed', err); 43 | }); 44 | } -------------------------------------------------------------------------------- /src/app-server.js: -------------------------------------------------------------------------------- 1 | import { createApp } from './app-universal'; 2 | 3 | export default (context) => { 4 | return new Promise((resolve, reject) => { 5 | const { app, router, store } = createApp(); 6 | router.push(context.url); 7 | 8 | router.onReady(() => { 9 | const matchedComponents = router.getMatchedComponents(); 10 | if (!matchedComponents.length) { 11 | reject({ code: 404, url: context.url }); 12 | } 13 | Promise.all(matchedComponents.map((Component) => { 14 | if (Component.asyncData) { 15 | return Component.asyncData({ 16 | store, 17 | route: router.currentRoute, 18 | }); 19 | } 20 | })) 21 | .then(() => { 22 | context.state = store.state; 23 | resolve(app); 24 | }) 25 | .catch(reject); 26 | }, reject); 27 | }); 28 | }; 29 | -------------------------------------------------------------------------------- /src/app-universal.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { createRouter } from './router'; 3 | import { createStore } from './store'; 4 | import { sync } from 'vuex-router-sync'; 5 | 6 | import App from './App.vue'; 7 | 8 | export const createApp = () => { 9 | const router = createRouter(); 10 | const store = createStore(); 11 | 12 | sync(store, router); 13 | 14 | const app = new Vue({ 15 | router, 16 | store, 17 | render: h => h(App), 18 | }); 19 | return { app, router, store }; 20 | }; 21 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Vue.js Memos 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
42 | 43 |
44 | 45 | 46 | -------------------------------------------------------------------------------- /src/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 41 | 42 | 74 | -------------------------------------------------------------------------------- /src/models/Memo.js: -------------------------------------------------------------------------------- 1 | export default class Memo { 2 | constructor({ id, title, body }) { 3 | this.id = id; 4 | this.title = title; 5 | this.body = body; 6 | } 7 | 8 | static createNew() { 9 | return new Memo({ 10 | title: '', 11 | body: '', 12 | }); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/models/MemoManager.js: -------------------------------------------------------------------------------- 1 | const createMapIdIndex = (memos) => { 2 | const map = {}; 3 | memos.forEach((memo, index) => { 4 | map[memo.id] = index; 5 | }); 6 | return map; 7 | }; 8 | 9 | export default class MemoManager { 10 | constructor({ memos, idCounter }) { 11 | this.memos = memos || []; 12 | this.mapIdIndex = createMapIdIndex(this.memos); 13 | this.idCounter = idCounter || this.memos.length; 14 | } 15 | 16 | push(memo) { 17 | this.mapIdIndex[memo.id] = this.memos.length; 18 | this.memos.push(memo); 19 | } 20 | 21 | update(memo) { 22 | const index = this.mapIdIndex[memo.id]; 23 | this.memos[index] = memo; 24 | } 25 | 26 | getMemo(id) { 27 | return this.memos[this.mapIdIndex[id]]; 28 | } 29 | 30 | getNextId() { 31 | this.idCounter = this.idCounter + 1; 32 | return this.idCounter; 33 | } 34 | 35 | toJSON() { 36 | return JSON.stringify(this.memos); 37 | } 38 | 39 | static fromJSONString(jsonString) { 40 | const memos = JSON.parse(jsonString); 41 | const maxId = memos[memos.length - 1].id; 42 | return new MemoManager({ 43 | memos, 44 | idCounter: maxId, 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/pages/memo.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 70 | 71 | 87 | -------------------------------------------------------------------------------- /src/pages/memos.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 32 | 33 | 41 | -------------------------------------------------------------------------------- /src/parts/global-footer.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 18 | -------------------------------------------------------------------------------- /src/parts/global-header.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 22 | 23 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router'; 3 | import routes from './routes'; 4 | 5 | Vue.use(VueRouter); 6 | 7 | export const createRouter = () => { 8 | return new VueRouter({ 9 | mode: 'history', 10 | routes, 11 | }); 12 | }; 13 | -------------------------------------------------------------------------------- /src/router/routes.js: -------------------------------------------------------------------------------- 1 | import Memos from '~/pages/memos.vue'; 2 | import Memo from '~/pages/memo.vue'; 3 | 4 | export default [ 5 | { 6 | path: '', 7 | component: Memos, 8 | }, 9 | { 10 | path: '/memos/:id', 11 | component: Memo, 12 | }, 13 | { 14 | path: '*', 15 | component: Memos, 16 | }, 17 | ]; 18 | -------------------------------------------------------------------------------- /src/scss/_variables.scss: -------------------------------------------------------------------------------- 1 | $mdc-theme-primary: #35495E; 2 | $mdc-theme-accent: #42b983; 3 | $mdc-theme-background: #fff; 4 | 5 | $c-vue: #42b983; 6 | $c-vue-dark: #35495E; 7 | 8 | $bp-sp: 480px; 9 | -------------------------------------------------------------------------------- /src/static/favicons/android-chrome-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mya-ake/vuejs-pwa-and-ssr-on-lambda/4e6a97f067aa5a110b8ec0404d533257a90a71ae/src/static/favicons/android-chrome-144x144.png -------------------------------------------------------------------------------- /src/static/favicons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mya-ake/vuejs-pwa-and-ssr-on-lambda/4e6a97f067aa5a110b8ec0404d533257a90a71ae/src/static/favicons/android-chrome-192x192.png -------------------------------------------------------------------------------- /src/static/favicons/android-chrome-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mya-ake/vuejs-pwa-and-ssr-on-lambda/4e6a97f067aa5a110b8ec0404d533257a90a71ae/src/static/favicons/android-chrome-36x36.png -------------------------------------------------------------------------------- /src/static/favicons/android-chrome-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mya-ake/vuejs-pwa-and-ssr-on-lambda/4e6a97f067aa5a110b8ec0404d533257a90a71ae/src/static/favicons/android-chrome-384x384.png -------------------------------------------------------------------------------- /src/static/favicons/android-chrome-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mya-ake/vuejs-pwa-and-ssr-on-lambda/4e6a97f067aa5a110b8ec0404d533257a90a71ae/src/static/favicons/android-chrome-48x48.png -------------------------------------------------------------------------------- /src/static/favicons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mya-ake/vuejs-pwa-and-ssr-on-lambda/4e6a97f067aa5a110b8ec0404d533257a90a71ae/src/static/favicons/android-chrome-512x512.png -------------------------------------------------------------------------------- /src/static/favicons/android-chrome-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mya-ake/vuejs-pwa-and-ssr-on-lambda/4e6a97f067aa5a110b8ec0404d533257a90a71ae/src/static/favicons/android-chrome-72x72.png -------------------------------------------------------------------------------- /src/static/favicons/android-chrome-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mya-ake/vuejs-pwa-and-ssr-on-lambda/4e6a97f067aa5a110b8ec0404d533257a90a71ae/src/static/favicons/android-chrome-96x96.png -------------------------------------------------------------------------------- /src/static/favicons/apple-touch-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mya-ake/vuejs-pwa-and-ssr-on-lambda/4e6a97f067aa5a110b8ec0404d533257a90a71ae/src/static/favicons/apple-touch-icon-114x114.png -------------------------------------------------------------------------------- /src/static/favicons/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mya-ake/vuejs-pwa-and-ssr-on-lambda/4e6a97f067aa5a110b8ec0404d533257a90a71ae/src/static/favicons/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /src/static/favicons/apple-touch-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mya-ake/vuejs-pwa-and-ssr-on-lambda/4e6a97f067aa5a110b8ec0404d533257a90a71ae/src/static/favicons/apple-touch-icon-144x144.png -------------------------------------------------------------------------------- /src/static/favicons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mya-ake/vuejs-pwa-and-ssr-on-lambda/4e6a97f067aa5a110b8ec0404d533257a90a71ae/src/static/favicons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /src/static/favicons/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mya-ake/vuejs-pwa-and-ssr-on-lambda/4e6a97f067aa5a110b8ec0404d533257a90a71ae/src/static/favicons/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /src/static/favicons/apple-touch-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mya-ake/vuejs-pwa-and-ssr-on-lambda/4e6a97f067aa5a110b8ec0404d533257a90a71ae/src/static/favicons/apple-touch-icon-57x57.png -------------------------------------------------------------------------------- /src/static/favicons/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mya-ake/vuejs-pwa-and-ssr-on-lambda/4e6a97f067aa5a110b8ec0404d533257a90a71ae/src/static/favicons/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /src/static/favicons/apple-touch-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mya-ake/vuejs-pwa-and-ssr-on-lambda/4e6a97f067aa5a110b8ec0404d533257a90a71ae/src/static/favicons/apple-touch-icon-72x72.png -------------------------------------------------------------------------------- /src/static/favicons/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mya-ake/vuejs-pwa-and-ssr-on-lambda/4e6a97f067aa5a110b8ec0404d533257a90a71ae/src/static/favicons/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /src/static/favicons/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mya-ake/vuejs-pwa-and-ssr-on-lambda/4e6a97f067aa5a110b8ec0404d533257a90a71ae/src/static/favicons/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /src/static/favicons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mya-ake/vuejs-pwa-and-ssr-on-lambda/4e6a97f067aa5a110b8ec0404d533257a90a71ae/src/static/favicons/apple-touch-icon.png -------------------------------------------------------------------------------- /src/static/favicons/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | #da532c 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/static/favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mya-ake/vuejs-pwa-and-ssr-on-lambda/4e6a97f067aa5a110b8ec0404d533257a90a71ae/src/static/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /src/static/favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mya-ake/vuejs-pwa-and-ssr-on-lambda/4e6a97f067aa5a110b8ec0404d533257a90a71ae/src/static/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /src/static/favicons/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mya-ake/vuejs-pwa-and-ssr-on-lambda/4e6a97f067aa5a110b8ec0404d533257a90a71ae/src/static/favicons/favicon-96x96.png -------------------------------------------------------------------------------- /src/static/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mya-ake/vuejs-pwa-and-ssr-on-lambda/4e6a97f067aa5a110b8ec0404d533257a90a71ae/src/static/favicons/favicon.ico -------------------------------------------------------------------------------- /src/static/favicons/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mya-ake/vuejs-pwa-and-ssr-on-lambda/4e6a97f067aa5a110b8ec0404d533257a90a71ae/src/static/favicons/mstile-144x144.png -------------------------------------------------------------------------------- /src/static/favicons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mya-ake/vuejs-pwa-and-ssr-on-lambda/4e6a97f067aa5a110b8ec0404d533257a90a71ae/src/static/favicons/mstile-150x150.png -------------------------------------------------------------------------------- /src/static/favicons/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mya-ake/vuejs-pwa-and-ssr-on-lambda/4e6a97f067aa5a110b8ec0404d533257a90a71ae/src/static/favicons/mstile-310x150.png -------------------------------------------------------------------------------- /src/static/favicons/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mya-ake/vuejs-pwa-and-ssr-on-lambda/4e6a97f067aa5a110b8ec0404d533257a90a71ae/src/static/favicons/mstile-310x310.png -------------------------------------------------------------------------------- /src/static/favicons/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mya-ake/vuejs-pwa-and-ssr-on-lambda/4e6a97f067aa5a110b8ec0404d533257a90a71ae/src/static/favicons/mstile-70x70.png -------------------------------------------------------------------------------- /src/static/favicons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 14 | 16 | 18 | 20 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/static/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "lang": "ja", 3 | "version": "1.0.0", 4 | "name": "Vue.js Memos", 5 | "short_name": "Vue.js Memos", 6 | "theme_color": "#42b983", 7 | "background_color": "#fff", 8 | "start_url": "./", 9 | "display": "standalone", 10 | "icons": [ 11 | { 12 | "src": "/favicons/android-chrome-36x36.png", 13 | "sizes": "36x36", 14 | "type": "image/png" 15 | }, 16 | { 17 | "src": "/favicons/android-chrome-48x48.png", 18 | "sizes": "48x48", 19 | "type": "image/png" 20 | }, 21 | { 22 | "src": "/favicons/android-chrome-72x72.png", 23 | "sizes": "72x72", 24 | "type": "image/png" 25 | }, 26 | { 27 | "src": "/favicons/android-chrome-96x96.png", 28 | "sizes": "96x96", 29 | "type": "image/png" 30 | }, 31 | { 32 | "src": "/favicons/android-chrome-144x144.png", 33 | "sizes": "144x144", 34 | "type": "image/png" 35 | }, 36 | { 37 | "src": "/favicons/android-chrome-192x192.png", 38 | "sizes": "192x192", 39 | "type": "image/png" 40 | }, 41 | { 42 | "src": "/favicons/android-chrome-384x384.png", 43 | "sizes": "384x384", 44 | "type": "image/png" 45 | }, 46 | { 47 | "src": "/favicons/android-chrome-512x512.png", 48 | "sizes": "512x512", 49 | "type": "image/png" 50 | } 51 | ] 52 | } -------------------------------------------------------------------------------- /src/store/control.js: -------------------------------------------------------------------------------- 1 | const TYPES = Object.freeze({ 2 | SET_SHOW_ADD_BUTTON: 'SET_SHOW_ADD_BUTTON', 3 | }); 4 | 5 | const stateObject = { 6 | isShowAddButton: true, 7 | }; 8 | 9 | const mutations = { 10 | [TYPES.SET_SHOW_ADD_BUTTON](state, payload) { 11 | state.isShowAddButton = payload; 12 | }, 13 | }; 14 | 15 | const actions = { 16 | setShowAddButton({ commit }, bool) { 17 | commit(TYPES.SET_SHOW_ADD_BUTTON, bool); 18 | }, 19 | }; 20 | 21 | export default { 22 | namespaced: true, 23 | state: stateObject, 24 | mutations, 25 | actions, 26 | }; 27 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | 4 | import moduleMemo from './memo'; 5 | import moduleControl from './control'; 6 | 7 | Vue.use(Vuex); 8 | 9 | export const createStore = () => { 10 | return new Vuex.Store({ 11 | modules: { 12 | memo: moduleMemo, 13 | control: moduleControl, 14 | }, 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /src/store/memo.js: -------------------------------------------------------------------------------- 1 | import Memo from '~/models/Memo'; 2 | import MemoManager from '~/models/MemoManager'; 3 | 4 | const TYPES = Object.freeze({ 5 | PUSH: 'PUSH', 6 | UPDATE: 'UPDATE', 7 | INIT: 'INIT', 8 | }); 9 | 10 | const KEYS = Object.freeze({ 11 | MEMOS: 'memos', 12 | }); 13 | 14 | const getMemoManager = () => { 15 | try { 16 | const localMemos = window.localStorage.getItem(KEYS.MEMOS); 17 | return localMemos === null ? new MemoManager({}) : MemoManager.fromJSONString(localMemos); 18 | } catch (err) { 19 | return new MemoManager({}); 20 | } 21 | }; 22 | 23 | const stateObject = () => ({ 24 | memoManager: getMemoManager(), 25 | }); 26 | 27 | const getters = { 28 | getMemo(state) { 29 | return (id) => { 30 | return state.memoManager.getMemo(id); 31 | }; 32 | }, 33 | getMemos(state) { 34 | return state.memoManager.memos; 35 | }, 36 | }; 37 | 38 | const mutations = { 39 | [TYPES.PUSH](state, memo) { 40 | state.memoManager.push(memo); 41 | }, 42 | [TYPES.UPDATE](state, memo) { 43 | state.memoManager.update(memo); 44 | }, 45 | [TYPES.INIT](state, memoManager) { 46 | state.memoManager = memoManager; 47 | }, 48 | }; 49 | 50 | const actions = { 51 | init({ commit }) { 52 | commit(TYPES.INIT, getMemoManager()); 53 | }, 54 | save({ commit, state }, { id, title, body }) { 55 | return new Promise((resolve) => { 56 | const isCreate = typeof id === 'undefined'; 57 | const memo = new Memo({ 58 | id: isCreate === true ? state.memoManager.getNextId() : id, 59 | title, 60 | body, 61 | }); 62 | const mutationType = isCreate === true ? TYPES.PUSH : TYPES.UPDATE; 63 | commit(mutationType, memo); 64 | resolve(memo); 65 | }); 66 | }, 67 | saveLocal({ state }) { 68 | window.localStorage.setItem(KEYS.MEMOS, state.memoManager.toJSON()); 69 | }, 70 | }; 71 | 72 | export default { 73 | namespaced: true, 74 | state: stateObject, 75 | getters, 76 | mutations, 77 | actions, 78 | }; 79 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | @import '_variables'; 2 | 3 | @import '@material/theme/mdc-theme'; 4 | @import '@material/toolbar/mdc-toolbar'; 5 | @import '@material/typography/mdc-typography'; 6 | @import '@material/fab/mdc-fab'; 7 | @import '@material/ripple/mdc-ripple'; 8 | @import '@material/list/mdc-list'; 9 | @import '@material/textfield/mdc-textfield'; 10 | @import '@material/button/mdc-button'; 11 | @import '@material/snackbar/mdc-snackbar'; 12 | --------------------------------------------------------------------------------