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"),
minimist = require("minimist"),
log = require("fancy-log"),
insert = require("gulp-insert"),
plumber = require("gulp-plumber"),
concat = require("gulp-concat"),
fs = require("fs"),
@ -21,10 +20,10 @@ const babel = require("gulp-babel"),
uglify = require("gulp-uglify-es").default;
// Vue packages
const browserify = require("browserify"),
vueify = require("vueify-next"),
source = require("vinyl-source-stream"),
buffer = require("vinyl-buffer");
const webpack = require("webpack"),
terserWebpackPlugin = require("terser-webpack-plugin"),
{ VueLoaderPlugin } = require("vue-loader"),
path = require("path");
// Determine if gulp has been run with --production
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
let browserSync = undefined;
if (!isProduction) {
if (isProduction) {
process.env.NODE_ENV = "production";
} else {
browserSync = require("browser-sync").create();
}
// Declare plugin settings
const sassOutputStyle = isProduction ? "compressed" : "expanded",
sassPaths = [ "node_modules" ],
autoprefixerSettings = { remove: false, cascade: false },
vuePaths = [ "./node_modules", "./resources/components", "./resources/js" ];
autoprefixerSettings = { remove: false, cascade: false };
// Javascript files for the public site
const jsPublic = "resources/js/app.js";
@ -120,43 +120,86 @@ function processCSS(outputFilename, inputFiles) {
}
// Process vue
function processVue(outputFilename, inputFile) {
const processedDir = "storage/app/",
processedFile = `__${outputFilename}.js`;
function processVue(outputFilename, inputFile, done) {
webpack({
mode: isProduction ? "production" : "development",
entry: [ `./${inputFile}` ],
output: { path: path.resolve(__dirname, "public/js"), filename: `${outputFilename}.js` },
devtool: false,
const preProcess = () => {
const javascript = gulp.src([ inputFile ]);
performance: {
maxEntrypointSize: 500000,
maxAssetSize: 500000
},
if (isProduction) {
javascript.pipe(insert.transform(function(contents) {
return contents.replace(/vue\.js/, "vue.min.js");
}));
resolve: {
alias: {
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))
.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();
done();
});
}
// Process javascript
@ -210,8 +253,8 @@ gulp.task("css-dashboard-libs", () => {
});
// Task for public javascript
gulp.task("js-public", () => {
return processVue("app", jsPublic);
gulp.task("js-public", (done) => {
return processVue("app", jsPublic, done);
});
// 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"
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
msg "Running: ${c_m}php artisan up"

12907
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

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

View file

@ -3,7 +3,24 @@
A Hypothetical website template for bootstrapping new projects.
* 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
@ -64,7 +81,12 @@ BrowserSync is used to keep the browser in sync with your code when running the
## 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:

View file

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

View file

@ -90,17 +90,17 @@
this.submitting = true;
$(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
$(this.$el).find(":input").attr("disabled", true);
this.errorCount = 0;
this.submitSuccess = true;
this.submitting = false;
}, (response) => {
}).catch((error) => {
// Error
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) {
$(this.$el).find(`[name='${errorName}']`).addClass("error");
errors++;

View file

@ -31,17 +31,17 @@
this.notifyStatus = "";
$(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
$(this.$el).find(":input").fadeOut(150);
this.notifyText = "Thanks for subscribing!";
this.notifyStatus = "success";
this.submitting = false;
}, (response) => {
}).catch((error) => {
// Error
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) {
$(this.$el).find(`[name='${errorName}']`).addClass("error");
errors++;

View file

@ -1,19 +1,22 @@
// Import features from Vue
import { createApp, nextTick } from "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 VueRouter from "vue-router";
import VueResource from "vue-resource";
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 { createRouter, createWebHistory } from "vue-router";
import { createStore } from "vuex";
// Import local javascript
import SupportsWebP from "imports/supports-webp.js";
@ -41,8 +44,8 @@ import ContactPage from "pages/contact.vue";
import Error404Page from "pages/error404.vue";
// Create a router instance
const router = new VueRouter({
mode: "history",
const router = new createRouter({
history: createWebHistory(),
linkActiveClass: "active",
routes: [
@ -64,7 +67,7 @@ const router = new VueRouter({
});
// Create a vuex store instance
const store = new Vuex.Store({
const store = createStore({
state: {
appName: env.appName,
appLang: env.appLang,
@ -127,9 +130,6 @@ const store = new Vuex.Store({
// Detect webp support
SupportsWebP.detect(store);
// Sync vue-router-sync with vuex store
sync(store, router);
// Functionality to run before page load and change
router.beforeEach((to, from, next) => {
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
store.commit("setFirstLoad", false);
} else {
Vue.nextTick(() => {
nextTick(() => {
TweenMax.to("#router-view", 0.25, { opacity: 1 });
});
}
}
});
const App = new Vue({
router,
store
}).$mount("#vue-container");
Vue.use(router).use(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
let browserSync = undefined;
if (!isProduction) {
if (isProduction) {
process.env.NODE_ENV = "production";
} else {
browserSync = require("browser-sync").create();
}

View file

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