From 84f7723e07a44685493f17b5324eabe424678f5c Mon Sep 17 00:00:00 2001 From: Vivian Guillen Date: Sun, 17 Jul 2022 16:37:32 -0400 Subject: [PATCH] Migrate to vite --- .config/.browserslistrc | 10 -- .config/vite.config.js | 55 ++++++++ .config/webpack/webpack.common.js | 92 ------------ .config/webpack/webpack.dev.js | 25 ---- .config/webpack/webpack.prod.js | 44 ------ package.json | 52 +++---- src/main.js | 227 +++++++++++++++--------------- 7 files changed, 187 insertions(+), 318 deletions(-) delete mode 100644 .config/.browserslistrc create mode 100644 .config/vite.config.js delete mode 100644 .config/webpack/webpack.common.js delete mode 100644 .config/webpack/webpack.dev.js delete mode 100644 .config/webpack/webpack.prod.js diff --git a/.config/.browserslistrc b/.config/.browserslistrc deleted file mode 100644 index f9018f91..00000000 --- a/.config/.browserslistrc +++ /dev/null @@ -1,10 +0,0 @@ -# docs: https://www.npmjs.com/package/browserslist -# run '$ npx browserslist' in .config/ directory to see wich browsers are selected by your queries for [production]. - -[development] -last 2 chrome versions -last 2 firefox versions - -[production] -last 4 versions -not IE <= 11 \ No newline at end of file diff --git a/.config/vite.config.js b/.config/vite.config.js new file mode 100644 index 00000000..b2c9b2db --- /dev/null +++ b/.config/vite.config.js @@ -0,0 +1,55 @@ +import { resolve } from "path"; +import vue from "@vitejs/plugin-vue"; +import del from "rollup-plugin-delete"; +import ESLintPlugin from '@modyqyw/vite-plugin-eslint'; +import StylelintPlugin from 'vite-plugin-stylelint'; + +export default { + plugins: [ + vue(), + ESLintPlugin({ + overrideConfigFile: resolve(__dirname, "./.eslintrc.js"), + include: "../src/**/*.{js,vue}", + }), + StylelintPlugin({ + files: '../src/**/*.{vue,css,sass,scss}', + configFile: resolve(__dirname, './.stylelintrc.js') + }) + ], + clearScreen: false, + css: { + postcss: resolve(__dirname, "../.config/postcss.config.js"), + }, + resolve: { + extensions: ["*", ".js", ".vue", ".json"], + alias: { + vue: "vue/dist/vue.esm-bundler.js", + "@": resolve(__dirname, "../src/"), + "@shopify-directory": resolve(__dirname, "../shopify/"), + }, + }, + build: { + rollupOptions: { + input: { + bundle: resolve(__dirname, "../src/main.js"), + }, + output: { + entryFileNames: `[name].js`, + chunkFileNames: `[name].js`, + assetFileNames: (assetInfo) => + assetInfo.name.split("/").pop().split(".").shift() == "main" + ? "bundle.css" + : "[name].[ext]", + }, + plugins: [ + process.env.NODE_ENV == "production" && + del({ + targets: ["../shopify/assets/**/*", "!../shopify/assets/*static*"], + }), + ], + }, + outDir: resolve(__dirname, "../shopify/assets"), + assetsDir: ".", + emptyOutDir: false, + }, +}; diff --git a/.config/webpack/webpack.common.js b/.config/webpack/webpack.common.js deleted file mode 100644 index 6bf6da9d..00000000 --- a/.config/webpack/webpack.common.js +++ /dev/null @@ -1,92 +0,0 @@ -const path = require('path') -const webpack = require('webpack') -const { CleanWebpackPlugin } = require('clean-webpack-plugin') -const MiniCssExtractPlugin = require('mini-css-extract-plugin') -const { VueLoaderPlugin } = require('vue-loader') - -module.exports = { - stats: 'minimal', - entry: path.resolve(__dirname, '../../src/main.js'), - output: { - path: path.resolve(__dirname, '../../shopify/assets/'), - filename: 'bundle.js' - }, - resolve: { - extensions: ['*', '.js', '.vue', '.json'], - alias: { - 'vue$': 'vue/dist/vue.esm-bundler.js', - '@': path.resolve(__dirname, '../../src/'), - '@shopify-directory': path.resolve(__dirname, '../../shopify/') - } - }, - module: { - rules: [ - { - test: /\.vue$/, - loader: 'vue-loader' - }, - { - test: /\.(png|svg|jpg|gif)$/, - use: [ - { - loader: 'url-loader', - options: { - limit: 8192 - } - } - ] - }, - ... (() => { - const rules = [] - - const loaders = [ - { test: /\.(css|postcss)$/i }, - { test: /\.s[ac]ss$/i, loader: 'sass-loader' }, - { test: /\.less$/i, loader: 'less-loader' }, - { test: /\.styl$/i, loader: 'stylus-loader' } - ] - - loaders.forEach((element, index) => { - rules.push({ - test: element.test, - use: [ - MiniCssExtractPlugin.loader, - 'css-loader', - { - loader: 'postcss-loader', - options: { - postcssOptions: require(path.resolve(__dirname, '../postcss.config.js')) - } - } - ] - }) - - if (element.loader) rules[index].use.push(element.loader) - }) - - return rules - })() - ] - }, - plugins: [ - /** - * don't clean files with the 'static' keyword in their filename - * docs: https://github.com/johnagan/clean-webpack-plugin - */ - new CleanWebpackPlugin({ - cleanOnceBeforeBuildPatterns: ['**/*', '!*static*'] - }), - /** - * docs: https://webpack.js.org/plugins/mini-css-extract-plugin - */ - new MiniCssExtractPlugin({ - filename: './bundle.css', - chunkFilename: '[id].css' - }), - new VueLoaderPlugin(), - new webpack.DefinePlugin({ - __VUE_OPTIONS_API__: 'true', - __VUE_PROD_DEVTOOLS__: 'false' - }) - ] -} \ No newline at end of file diff --git a/.config/webpack/webpack.dev.js b/.config/webpack/webpack.dev.js deleted file mode 100644 index fa933854..00000000 --- a/.config/webpack/webpack.dev.js +++ /dev/null @@ -1,25 +0,0 @@ -const path = require('path') -const { merge } = require('webpack-merge') -const ESLintPlugin = require('eslint-webpack-plugin') -const StylelintPlugin = require('stylelint-webpack-plugin') -const common = require('./webpack.common.js') - -module.exports = merge(common, { - mode: 'development', - plugins: [ - /** - * docs: https://www.npmjs.com/package/eslint-webpack-plugin - */ - new ESLintPlugin({ - files: 'src/**/*.{js,vue}', - overrideConfigFile: path.resolve(__dirname, '../.eslintrc.js') - }), - /** - * docs: https://www.npmjs.com/package/stylelint-webpack-plugin - */ - new StylelintPlugin({ - files: 'src/**/*.{vue,css,sass,scss}', - configFile: path.resolve(__dirname, '../.stylelintrc.js') - }) - ] -}) \ No newline at end of file diff --git a/.config/webpack/webpack.prod.js b/.config/webpack/webpack.prod.js deleted file mode 100644 index 6635155d..00000000 --- a/.config/webpack/webpack.prod.js +++ /dev/null @@ -1,44 +0,0 @@ -const { merge } = require('webpack-merge') -const TerserPlugin = require('terser-webpack-plugin') // included in webpack 5, no need to add to package.json -const CssMinimizerPlugin = require('css-minimizer-webpack-plugin') -const common = require('./webpack.common.js') - -module.exports = merge(common, { - mode: 'production', - performance: { - hints: false - }, - module: { - rules: [ - { - test: /\.js$/, - loader: 'babel-loader', - exclude: /node_modules/, - options: { - presets: [ '@babel/preset-env' ], - plugins: [ '@babel/plugin-transform-runtime' ] - } - } - ] - }, - optimization: { - minimize: true, - minimizer: [ - /** - * docs: https://webpack.js.org/plugins/terser-webpack-plugin - */ - new TerserPlugin({ - terserOptions: { - format: { - comments: /@license/i // preserve license comments - } - }, - extractComments: false - }), - /** - * docs: https://webpack.js.org/plugins/css-minimizer-webpack-plugin - */ - new CssMinimizerPlugin() - ] - } -}) \ No newline at end of file diff --git a/package.json b/package.json index 11b6cc84..28321ada 100644 --- a/package.json +++ b/package.json @@ -5,11 +5,11 @@ "version": "4.4.1", "license": "MIT", "scripts": { - "start": "run-p -sr shopify:serve webpack:watch", - "deploy": "run-s webpack:build && cd shopify && shopify theme push", - "deploy:new": "run-s webpack:build && cd shopify && shopify theme push --unpublished", - "webpack:watch": "cross-env NODE_ENV=development BROWSERSLIST_ENV=development BROWSERSLIST_CONFIG=.config/.browserslistrc webpack --config .config/webpack/webpack.dev.js --watch --progress", - "webpack:build": "cross-env NODE_ENV=production BROWSERSLIST_ENV=production BROWSERSLIST_CONFIG=.config/.browserslistrc webpack --config .config/webpack/webpack.prod.js --progress", + "start": "run-p -sr shopify:serve vite:watch", + "deploy": "run-s vite:build && cd shopify && shopify theme push", + "deploy:new": "run-s vite:build && cd shopify && shopify theme push --unpublished", + "vite:watch": "cross-env NODE_ENV=development vite build --watch --mode 'development' --config .config/vite.config.js", + "vite:build": "cross-env NODE_ENV=production vite build --mode 'production' --config .config/vite.config.js", "shopify:serve": "cd shopify && shopify theme serve", "shopify:pull": "cd shopify && shopify theme pull", "lint": "run-s -c lint:*", @@ -22,37 +22,25 @@ "fix:shopify": "cd shopify && shopify theme check -a" }, "dependencies": { - "tailwindcss": "^3.0.2", - "vue": "^3.2.26", + "tailwindcss": "^3.1.6", + "vue": "^3.2.37", "vuex": "^4.0.2" }, "devDependencies": { - "@babel/core": "^7.16.0", - "@babel/plugin-transform-runtime": "^7.16.4", - "@babel/preset-env": "^7.16.4", - "@vue/compiler-sfc": "^3.2.26", - "autoprefixer": "^10.4.0", - "babel-loader": "^8.2.3", - "clean-webpack-plugin": "^4.0.0", + "@vitejs/plugin-vue": "^3.0.0", + "vite": "^3.0.0", + "@modyqyw/vite-plugin-eslint": "^2.0.3", + "rollup-plugin-delete": "^2.0.0", + "autoprefixer": "^10.4.7", "cross-env": "^7.0.3", - "css-loader": "^6.5.1", - "css-minimizer-webpack-plugin": "^3.2.0", - "eslint": "^8.4.1", - "eslint-plugin-vue": "^8.2.0", - "eslint-webpack-plugin": "^3.1.1", - "mini-css-extract-plugin": "^2.4.5", + "eslint": "^8.20.0", + "eslint-plugin-vue": "^9.2.0", "npm-run-all": "^4.1.5", - "postcss": "^8.4.5", - "postcss-html": "^1.3.0", - "postcss-import": "^14.0.2", - "postcss-loader": "^6.2.1", - "stylelint": "^14.1.0", - "stylelint-config-recommended": "^6.0.0", - "stylelint-webpack-plugin": "^3.1.0", - "url-loader": "^4.1.1", - "vue-loader": "^16.8.3", - "webpack": "^5.65.0", - "webpack-cli": "^4.9.1", - "webpack-merge": "^5.8.0" + "postcss": "^8.4.14", + "postcss-html": "^1.5.0", + "postcss-import": "^14.1.0", + "stylelint": "^14.9.1", + "stylelint-config-recommended": "^8.0.0", + "vite-plugin-stylelint": "^3.0.2" } } diff --git a/src/main.js b/src/main.js index ace6af4c..72bbb90a 100644 --- a/src/main.js +++ b/src/main.js @@ -1,118 +1,115 @@ /** * imports */ -import { createApp } from 'vue' -import { createStore } from 'vuex' -import './css/main.css' - -/** - * vuex - * auto-import all modules and prepare shared store - */ -const vuexModules = require.context('./vue/store/', true, /\.js$/) -const modules = {} - -vuexModules.keys().forEach(key => { - const name = key.replace(/\.(\/|js)/g, '').replace(/\s/g, '-') - modules[name] = vuexModules(key).default -}) - -const store = createStore({ - strict: process.env.NODE_ENV !== 'production', - modules -}) - -/** - * create vue instance function - */ -const createVueApp = () => { - const app = createApp({}) - - /** - * vue components - * auto-import all vue components - */ - const vueComponents = require.context('./vue/components/', true, /\.(vue|js)$/) - - vueComponents.keys().forEach(key => { - const component = vueComponents(key).default - - // if a component has a name defined use the name, else use the path as the component name - const name = component.name - ? component.name - : key.replace(/\.(\/|vue|js)/g, '').replace(/(\/|-|_|\s)\w/g, (match) => match.slice(1).toUpperCase()) - - app.component(name, component) - }) - - /** - * vue mixins - * auto-register all mixins with a 'global' keyword in their filename - */ - const mixins = require.context('./vue/mixins/', true, /.*global.*\.js$/) - - mixins.keys().forEach(key => { - app.mixin(mixins(key).default) - }) - - /** - * vue directives - * auto-register all directives with a 'global' keyword in their filename - */ - const directives = require.context('./vue/directives/', true, /.*global.*\.js$/) - - directives.keys().forEach(key => { - const directive = directives(key).default - app.directive(directive.name, directive.directive) - }) - - /** - * vue plugins - * extend with additional features - */ - app.use(store) - - return app -} - -/** - * create and mount vue instance(s) - */ -const appElement = document.querySelector('#app') - -if (appElement) { - createVueApp().mount(appElement) -} else { - const vueElements = document.querySelectorAll('[vue]') - if (vueElements) vueElements.forEach(el => createVueApp().mount(el)) -} - -/** - * fixes for Shopify sections - * 1. properly render vue components on user insert in the theme editor - * 2. reload the current page to rerender async inserted sections with vue components - * - * add the 'vue' keyword to the section's wrapper classes if the section uses any vue functionality e.g.: - * {% schema %} - * { - * "class": "vue-section" - * } - * {% endschema %} - */ -if (Shopify.designMode) { - document.addEventListener('shopify:section:load', (event) => { - if (event.target.classList.value.includes('vue')) { - createVueApp().mount(event.target) - } - }) -} else if (!Shopify.designMode && process.env.NODE_ENV === 'development') { - new MutationObserver((mutationsList) => { - mutationsList.forEach(record => { - const vue = Array.from(record.addedNodes).find(node => node.classList && node.classList.value.includes('vue')) - if (vue) window.location.reload() - }) - }).observe(document.body, { - childList: true, - subtree: true - }) -} \ No newline at end of file + import { createApp } from 'vue' + import { createStore } from 'vuex' + import './css/main.css' + + /** + * vuex + * auto-import all modules and prepare shared store + */ + const vuexModules = import.meta.globEager('./vue/store/**/*.js') + const modules = {} + + Object.entries(vuexModules).forEach(([path, definition]) => { + const name = path.replace(/\/vue\/store/g, '').replace(/\.(\/|js)/g, '').replace(/\s/g, '-'); + + modules[name] = definition.default + }) + + const store = createStore({ + strict: process.env.NODE_ENV !== 'production', + modules + }) + + /** + * create vue instance function + */ + const createVueApp = () => { + const app = createApp({}) + + /** + * vue components + * auto-import all vue components + */ + const components = import.meta.globEager('./vue/components/**/*.vue') + + Object.entries(components).forEach(([path, definition]) => { + const componentName = path.replace(/\/vue\/components/g, '').replace(/\.(\/|vue|js)/g, '').replace(/(\/|-|_|\s)\w/g, (match) => match.slice(1).toUpperCase()); + + app.component(componentName, definition.default) + }) + + /** + * vue mixins + * auto-register all mixins with a 'global' keyword in their filename + */ + // const mixins = require.context('./vue/mixins/', true, /.*global.*\.js$/) + const mixins = import.meta.globEager('./vue/mixins/*.js') + + Object.entries(mixins).forEach(([path, definition]) => { + app.mixin(definition.default) + }) + + /** + * vue directives + * auto-register all directives with a 'global' keyword in their filename + */ + const directives = import.meta.globEager('./vue/directives/*.js') + + Object.entries(directives).forEach(([path, definition]) => { + const directive = definition.default + app.directive(directive.name, directive.directive) + }) + + /** + * vue plugins + * extend with additional features + */ + app.use(store) + + return app + } + + /** + * create and mount vue instance(s) + */ + const appElement = document.querySelector('#app') + + if (appElement) { + createVueApp().mount(appElement) + } else { + const vueElements = document.querySelectorAll('[vue]') + if (vueElements) vueElements.forEach(el => createVueApp().mount(el)) + } + + /** + * fixes for Shopify sections + * 1. properly render vue components on user insert in the theme editor + * 2. reload the current page to rerender async inserted sections with vue components + * + * add the 'vue' keyword to the section's wrapper classes if the section uses any vue functionality e.g.: + * {% schema %} + * { + * "class": "vue-section" + * } + * {% endschema %} + */ + if (Shopify.designMode) { + document.addEventListener('shopify:section:load', (event) => { + if (event.target.classList.value.includes('vue')) { + createVueApp().mount(event.target) + } + }) + } else if (!Shopify.designMode && process.env.NODE_ENV === 'development') { + new MutationObserver((mutationsList) => { + mutationsList.forEach(record => { + const vue = Array.from(record.addedNodes).find(node => node.classList && node.classList.value.includes('vue')) + if (vue) window.location.reload() + }) + }).observe(document.body, { + childList: true, + subtree: true + }) + }