Upgrade to Vue 3, replace vue-resource with axios, update other libraries, set the NODE_ENV to production in the gulpfile when --production is specified rather than in the init.sh, and improve the README

This commit is contained in:
Kevin MacMartin 2022-06-13 18:04:19 -04:00
parent 30f57e6082
commit ef617ac409
11 changed files with 3800 additions and 9381 deletions

View file

@ -2,7 +2,6 @@
const gulp = require("gulp"), const gulp = require("gulp"),
minimist = require("minimist"), minimist = require("minimist"),
log = require("fancy-log"), log = require("fancy-log"),
insert = require("gulp-insert"),
plumber = require("gulp-plumber"), plumber = require("gulp-plumber"),
concat = require("gulp-concat"), concat = require("gulp-concat"),
fs = require("fs"), fs = require("fs"),
@ -21,10 +20,10 @@ const babel = require("gulp-babel"),
uglify = require("gulp-uglify-es").default; uglify = require("gulp-uglify-es").default;
// Vue packages // Vue packages
const browserify = require("browserify"), const webpack = require("webpack"),
vueify = require("vueify-next"), terserWebpackPlugin = require("terser-webpack-plugin"),
source = require("vinyl-source-stream"), { VueLoaderPlugin } = require("vue-loader"),
buffer = require("vinyl-buffer"); path = require("path");
// Determine if gulp has been run with --production // Determine if gulp has been run with --production
const isProduction = minimist(process.argv.slice(2)).production !== undefined; const isProduction = minimist(process.argv.slice(2)).production !== undefined;
@ -32,15 +31,16 @@ const isProduction = minimist(process.argv.slice(2)).production !== undefined;
// Include browsersync when gulp has not been run with --production // Include browsersync when gulp has not been run with --production
let browserSync = undefined; let browserSync = undefined;
if (!isProduction) { if (isProduction) {
process.env.NODE_ENV = "production";
} else {
browserSync = require("browser-sync").create(); browserSync = require("browser-sync").create();
} }
// Declare plugin settings // Declare plugin settings
const sassOutputStyle = isProduction ? "compressed" : "expanded", const sassOutputStyle = isProduction ? "compressed" : "expanded",
sassPaths = [ "node_modules" ], sassPaths = [ "node_modules" ],
autoprefixerSettings = { remove: false, cascade: false }, autoprefixerSettings = { remove: false, cascade: false };
vuePaths = [ "./node_modules", "./resources/components", "./resources/js" ];
// Javascript files for the public site // Javascript files for the public site
const jsPublic = "resources/js/app.js"; const jsPublic = "resources/js/app.js";
@ -120,43 +120,86 @@ function processCSS(outputFilename, inputFiles) {
} }
// Process vue // Process vue
function processVue(outputFilename, inputFile) { function processVue(outputFilename, inputFile, done) {
const processedDir = "storage/app/", webpack({
processedFile = `__${outputFilename}.js`; mode: isProduction ? "production" : "development",
entry: [ `./${inputFile}` ],
output: { path: path.resolve(__dirname, "public/js"), filename: `${outputFilename}.js` },
devtool: false,
const preProcess = () => { performance: {
const javascript = gulp.src([ inputFile ]); maxEntrypointSize: 500000,
maxAssetSize: 500000
},
if (isProduction) { resolve: {
javascript.pipe(insert.transform(function(contents) { alias: {
return contents.replace(/vue\.js/, "vue.min.js"); vue$: "vue/dist/vue.esm-bundler.js",
})); vuex$: "vuex/dist/vuex.esm-bundler.js",
pages: path.resolve(__dirname, "resources/components/pages"),
sections: path.resolve(__dirname, "resources/components/sections"),
partials: path.resolve(__dirname, "resources/components/partials"),
mixins: path.resolve(__dirname, "resources/js/mixins"),
imports: path.resolve(__dirname, "resources/js/imports")
}
},
module: {
rules: [
{
test: /\.vue$/,
loader: "vue-loader",
options: { presets: [ [ "@babel/preset-env" ] ] }
},
{
test: /\.js$/,
loader: "babel-loader",
options: { presets: [ [ "@babel/preset-env" ] ] }
}
]
},
plugins: [
new webpack.DefinePlugin({ __VUE_OPTIONS_API__: true, __VUE_PROD_DEVTOOLS__: false }),
new VueLoaderPlugin()
],
optimization: {
minimizer: [
new terserWebpackPlugin({
extractComments: false,
terserOptions: {
format: { comments: false },
compress: { drop_console: isProduction }
}
})
]
}
}, (err, stats) => {
let statsJson;
if (err) {
log.error(err.stack || err);
if (err.details) {
log.error(err.details);
}
} else if (stats.hasWarnings() || stats.hasErrors()) {
statsString = stats.toString("errors-only", {
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
});
log.error(statsString);
} }
return javascript.pipe(concat(processedFile)) done();
.pipe(gulp.dest(processedDir)); });
};
const process = () => {
const javascript = browserify({
entries: [ processedDir + processedFile ],
paths: vuePaths
}).transform("babelify")
.transform(vueify)
.bundle()
.on("error", handleError)
.pipe(source(`${outputFilename}.js`))
.pipe(buffer());
if (isProduction) {
javascript.pipe(stripDebug()).pipe(uglify().on("error", handleError));
}
return javascript.pipe(gulp.dest("public/js/"));
};
preProcess();
return process();
} }
// Process javascript // Process javascript
@ -210,8 +253,8 @@ gulp.task("css-dashboard-libs", () => {
}); });
// Task for public javascript // Task for public javascript
gulp.task("js-public", () => { gulp.task("js-public", (done) => {
return processVue("app", jsPublic); return processVue("app", jsPublic, done);
}); });
// Task for public javascript libraries // Task for public javascript libraries

View file

@ -101,7 +101,7 @@ msg "Running: ${c_m}npm install --production"
npm install --production || error "${c_m}npm install --production$c_w exited with an error status" npm install --production || error "${c_m}npm install --production$c_w exited with an error status"
msg "Running: ${c_m}gulp --production" msg "Running: ${c_m}gulp --production"
NODE_ENV=production "$(npm bin)/gulp" --production || error "${c_m}gulp --production$c_w exited with an error status" "$(npm bin)/gulp" --production || error "${c_m}gulp --production$c_w exited with an error status"
if (( artisan_down )); then if (( artisan_down )); then
msg "Running: ${c_m}php artisan up" msg "Running: ${c_m}php artisan up"

12885
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -5,27 +5,26 @@
"dev": "gulp default watch" "dev": "gulp default watch"
}, },
"devDependencies": { "devDependencies": {
"browser-sync": "2.27.9" "browser-sync": "2.27.10"
}, },
"dependencies": { "dependencies": {
"@babel/core": "7.17.8", "@babel/core": "7.18.5",
"@babel/preset-env": "7.16.11", "@babel/preset-env": "7.18.2",
"@fortawesome/fontawesome-free": "5.15.4", "@fortawesome/fontawesome-free": "5.15.4",
"@mr-hope/gulp-sass": "2.0.0", "@mr-hope/gulp-sass": "2.0.0",
"autonumeric": "4.6.0", "autonumeric": "4.6.0",
"autoprefixer": "10.4.4", "autoprefixer": "10.4.5",
"babelify": "10.0.0", "axios": "0.27.2",
"babel-loader": "8.2.5",
"bootstrap": "5.1.3", "bootstrap": "5.1.3",
"browserify": "17.0.0",
"easymde": "2.16.1", "easymde": "2.16.1",
"fancy-log": "2.0.0", "fancy-log": "2.0.0",
"flatpickr": "4.6.11", "flatpickr": "4.6.13",
"gsap": "3.9.1", "gsap": "3.10.4",
"gulp": "4.0.2", "gulp": "4.0.2",
"gulp-babel": "8.0.0", "gulp-babel": "8.0.0",
"gulp-clean-css": "4.3.0", "gulp-clean-css": "4.3.0",
"gulp-concat": "2.6.1", "gulp-concat": "2.6.1",
"gulp-insert": "0.5.0",
"gulp-plumber": "1.2.1", "gulp-plumber": "1.2.1",
"gulp-postcss": "9.0.1", "gulp-postcss": "9.0.1",
"gulp-sass-glob": "1.1.0", "gulp-sass-glob": "1.1.0",
@ -35,19 +34,16 @@
"list.js": "2.3.1", "list.js": "2.3.1",
"minimist": "1.2.6", "minimist": "1.2.6",
"popper.js": "1.16.1", "popper.js": "1.16.1",
"postcss": "8.4.12", "postcss": "8.4.14",
"sass": "1.32.12", "sass": "1.32.12",
"sortablejs": "1.15.0", "sortablejs": "1.15.0",
"spinkit": "2.0.1", "spinkit": "2.0.1",
"vinyl-buffer": "1.0.1", "terser-webpack-plugin": "5.3.3",
"vinyl-source-stream": "2.0.0", "vue": "3.2.37",
"vue": "2.6.14", "vue-loader": "17.0.0",
"vue-resource": "1.5.3", "vue-router": "4.0.16",
"vue-router": "3.5.3", "vuex": "4.0.2",
"vue-template-compiler": "2.6.14", "webpack": "5.73.0",
"vueify-next": "9.6.0", "what-input": "5.2.12"
"vuex": "3.6.2",
"vuex-router-sync": "5.0.0",
"what-input": "5.2.10"
} }
} }

View file

@ -3,7 +3,24 @@
A Hypothetical website template for bootstrapping new projects. A Hypothetical website template for bootstrapping new projects.
* Written and maintained by Kevin MacMartin * Written and maintained by Kevin MacMartin
* Based on Laravel 9.1.8
## Features
* A choice between an SPA on top of a PHP backend or a pure SSR site (See the [Public](#public) section below for more information)
* A flexible dashboard for managing data/assets and displaying collected form data
* Great defaults and popular libraries to build on top of
* A custom Sass function allowing values to be specified in `px` and output in `rem`
## Major Components
* Bootstrap 5
* Fontawesome 5
* Gsap 3
* Gulp 4
* Jquery 3
* Laravel 9
* Sass 1.32
* Vue 3 (Optional)
## Setup ## Setup
@ -64,7 +81,12 @@ BrowserSync is used to keep the browser in sync with your code when running the
## Public ## Public
The default public facing website uses Vue.js. To configure a non-SPA traditional website, look at the files in `traditional-bootstrap`. The default public facing website is an SPA using Vue.js. To configure a non-SPA traditional SSR website remove the following files before moving the contents of `traditional-bootstrap` into the root project:
* `package-lock.json`
* `resources/components`
* `resources/js/mixins`
* `resources/js/imports`
The following list of files and directories are where various pieces of the public website are located: The following list of files and directories are where various pieces of the public website are located:

View file

@ -55,8 +55,8 @@
methods: { methods: {
populateBlogEntries() { populateBlogEntries() {
this.$http.get("/api/blog-entries" + env.apiToken).then((response) => { this.$http.get("/api/blog-entries" + env.apiToken).then((response) => {
this.blogEntries = response.body; this.blogEntries = response.data;
}, (response) => { }).catch((error) => {
console.log("Failed to retrieve blog entries"); console.log("Failed to retrieve blog entries");
}); });
} }

View file

@ -90,17 +90,17 @@
this.submitting = true; this.submitting = true;
$(this.$el).find(":input.error").removeClass("error"); $(this.$el).find(":input.error").removeClass("error");
this.$http.post("/api/contact-submit" + env.apiToken, JSON.stringify(this.form)).then((response) => { this.$http.post("/api/contact-submit" + env.apiToken, this.form).then((response) => {
// Success // Success
$(this.$el).find(":input").attr("disabled", true); $(this.$el).find(":input").attr("disabled", true);
this.errorCount = 0; this.errorCount = 0;
this.submitSuccess = true; this.submitSuccess = true;
this.submitting = false; this.submitting = false;
}, (response) => { }).catch((error) => {
// Error // Error
let errors = 0; let errors = 0;
for (let errorName in response.body.errors) { for (let errorName in error.response.data.errors) {
if ($(this.$el).find(`[name='${errorName}']`).length) { if ($(this.$el).find(`[name='${errorName}']`).length) {
$(this.$el).find(`[name='${errorName}']`).addClass("error"); $(this.$el).find(`[name='${errorName}']`).addClass("error");
errors++; errors++;

View file

@ -31,17 +31,17 @@
this.notifyStatus = ""; this.notifyStatus = "";
$(this.$el).find(":input.error").removeClass("error"); $(this.$el).find(":input.error").removeClass("error");
this.$http.post("/api/subscription-submit" + env.apiToken, JSON.stringify(this.form)).then((response) => { this.$http.post("/api/subscription-submit" + env.apiToken, this.form).then((response) => {
// Success // Success
$(this.$el).find(":input").fadeOut(150); $(this.$el).find(":input").fadeOut(150);
this.notifyText = "Thanks for subscribing!"; this.notifyText = "Thanks for subscribing!";
this.notifyStatus = "success"; this.notifyStatus = "success";
this.submitting = false; this.submitting = false;
}, (response) => { }).catch((error) => {
// Error // Error
let errors = 0; let errors = 0;
for (let errorName in response.body.errors) { for (let errorName in error.response.data.errors) {
if ($(this.$el).find(`[name='${errorName}']`).length) { if ($(this.$el).find(`[name='${errorName}']`).length) {
$(this.$el).find(`[name='${errorName}']`).addClass("error"); $(this.$el).find(`[name='${errorName}']`).addClass("error");
errors++; errors++;

View file

@ -1,19 +1,22 @@
// Import features from Vue
import { createApp, nextTick } from "vue";
// Initialize Vue // Initialize Vue
const Vue = require("vue/dist/vue.js"); const Vue = createApp({});
// Import and configure axios
window.axios = require("axios");
window.axios.defaults.headers.common = {
"X-Requested-With": "XMLHttpRequest",
"X-CSRF-TOKEN": env.csrfToken
};
Vue.config.globalProperties.$http = window.axios;
// Import plugins // Import plugins
import VueRouter from "vue-router"; import { createRouter, createWebHistory } from "vue-router";
import VueResource from "vue-resource"; import { createStore } from "vuex";
import Vuex from "vuex";
import { sync } from "vuex-router-sync";
// Load plugins
Vue.use(VueRouter);
Vue.use(VueResource);
Vue.use(Vuex);
// CSRF prevention header
Vue.http.headers.common["X-CSRF-TOKEN"] = env.csrfToken;
// Import local javascript // Import local javascript
import SupportsWebP from "imports/supports-webp.js"; import SupportsWebP from "imports/supports-webp.js";
@ -41,8 +44,8 @@ import ContactPage from "pages/contact.vue";
import Error404Page from "pages/error404.vue"; import Error404Page from "pages/error404.vue";
// Create a router instance // Create a router instance
const router = new VueRouter({ const router = new createRouter({
mode: "history", history: createWebHistory(),
linkActiveClass: "active", linkActiveClass: "active",
routes: [ routes: [
@ -64,7 +67,7 @@ const router = new VueRouter({
}); });
// Create a vuex store instance // Create a vuex store instance
const store = new Vuex.Store({ const store = createStore({
state: { state: {
appName: env.appName, appName: env.appName,
appLang: env.appLang, appLang: env.appLang,
@ -127,9 +130,6 @@ const store = new Vuex.Store({
// Detect webp support // Detect webp support
SupportsWebP.detect(store); SupportsWebP.detect(store);
// Sync vue-router-sync with vuex store
sync(store, router);
// Functionality to run before page load and change // Functionality to run before page load and change
router.beforeEach((to, from, next) => { router.beforeEach((to, from, next) => {
if (to.path !== store.getters.getLastPath) { if (to.path !== store.getters.getLastPath) {
@ -159,14 +159,11 @@ router.afterEach((to, from) => {
// Set Page.firstLoad to false so we know the initial load has completed // Set Page.firstLoad to false so we know the initial load has completed
store.commit("setFirstLoad", false); store.commit("setFirstLoad", false);
} else { } else {
Vue.nextTick(() => { nextTick(() => {
TweenMax.to("#router-view", 0.25, { opacity: 1 }); TweenMax.to("#router-view", 0.25, { opacity: 1 });
}); });
} }
} }
}); });
const App = new Vue({ Vue.use(router).use(store).mount("#vue-container");
router,
store
}).$mount("#vue-container");

View file

@ -25,7 +25,9 @@ const isProduction = minimist(process.argv.slice(2)).production !== undefined;
// Include browsersync when gulp has not been run with --production // Include browsersync when gulp has not been run with --production
let browserSync = undefined; let browserSync = undefined;
if (!isProduction) { if (isProduction) {
process.env.NODE_ENV = "production";
} else {
browserSync = require("browser-sync").create(); browserSync = require("browser-sync").create();
} }

View file

@ -5,20 +5,20 @@
"dev": "gulp default watch" "dev": "gulp default watch"
}, },
"devDependencies": { "devDependencies": {
"browser-sync": "2.27.9" "browser-sync": "2.27.10"
}, },
"dependencies": { "dependencies": {
"@babel/core": "7.17.8", "@babel/core": "7.18.5",
"@babel/preset-env": "7.16.11", "@babel/preset-env": "7.18.2",
"@fortawesome/fontawesome-free": "5.15.4", "@fortawesome/fontawesome-free": "5.15.4",
"@mr-hope/gulp-sass": "2.0.0", "@mr-hope/gulp-sass": "2.0.0",
"autonumeric": "4.6.0", "autonumeric": "4.6.0",
"autoprefixer": "10.4.4", "autoprefixer": "10.4.5",
"bootstrap": "5.1.3", "bootstrap": "5.1.3",
"easymde": "2.16.1", "easymde": "2.16.1",
"fancy-log": "2.0.0", "fancy-log": "2.0.0",
"flatpickr": "4.6.11", "flatpickr": "4.6.13",
"gsap": "3.9.1", "gsap": "3.10.4",
"gulp": "4.0.2", "gulp": "4.0.2",
"gulp-babel": "8.0.0", "gulp-babel": "8.0.0",
"gulp-clean-css": "4.3.0", "gulp-clean-css": "4.3.0",
@ -32,10 +32,10 @@
"list.js": "2.3.1", "list.js": "2.3.1",
"minimist": "1.2.6", "minimist": "1.2.6",
"popper.js": "1.16.1", "popper.js": "1.16.1",
"postcss": "8.4.12", "postcss": "8.4.14",
"sass": "1.32.12", "sass": "1.32.12",
"sortablejs": "1.15.0", "sortablejs": "1.15.0",
"spinkit": "2.0.1", "spinkit": "2.0.1",
"what-input": "5.2.10" "what-input": "5.2.12"
} }
} }