From 972130e7fc7853a4581bbc70ae282cdc4bb43a2c Mon Sep 17 00:00:00 2001 From: Fritz Lin Date: Fri, 2 Sep 2022 20:10:23 +0800 Subject: [PATCH] switching to vite !! + umd support -- #137 https://github.com/vuetifyjs/vuetify/discussions/4068#discussioncomment-1357093 https://github.com/vuejs/core/issues/4344#issuecomment-1023220225 --- .babelrc | 39 ------------ .eslintignore | 3 + .eslintrc.js | 38 ++++++++++++ .gitignore | 3 + README.md | 89 +++++++++++++--------------- build/ViteSingleCssPlugin.js | 47 +++++++++++++++ index.html | 9 +-- package.json | 61 +++++++------------ {static => public/demo}/awesome.svg | 0 {static => public/demo}/electron.svg | 0 public/demo/vite.svg | 1 + src/App.vue | 20 ++----- test_umd.html | 34 +++++++++++ vite.config.0.js | 35 +++++++++++ vite.config.1.js | 16 +++++ vite.config.2.js | 16 +++++ vite.demo.config.js | 62 +++++++++++++++++++ webpack/base.js | 64 -------------------- webpack/demo.js | 35 ----------- webpack/prod.js | 44 -------------- 20 files changed, 326 insertions(+), 290 deletions(-) delete mode 100644 .babelrc create mode 100644 .eslintignore create mode 100644 .eslintrc.js create mode 100644 build/ViteSingleCssPlugin.js rename {static => public/demo}/awesome.svg (100%) rename {static => public/demo}/electron.svg (100%) create mode 100644 public/demo/vite.svg create mode 100644 test_umd.html create mode 100644 vite.config.0.js create mode 100644 vite.config.1.js create mode 100644 vite.config.2.js create mode 100644 vite.demo.config.js delete mode 100644 webpack/base.js delete mode 100644 webpack/demo.js delete mode 100644 webpack/prod.js diff --git a/.babelrc b/.babelrc deleted file mode 100644 index e10f6de..0000000 --- a/.babelrc +++ /dev/null @@ -1,39 +0,0 @@ -{ - "presets": [ - "minify", - [ - "@babel/preset-env", - { - "modules": false - } - ] - ], - "plugins": [ - "@babel/plugin-syntax-dynamic-import", - "@babel/plugin-syntax-import-meta", - "@babel/plugin-proposal-class-properties", - "@babel/plugin-proposal-json-strings", - [ - "@babel/plugin-proposal-decorators", - { - "legacy": true - } - ], - "@babel/plugin-proposal-function-sent", - "@babel/plugin-proposal-export-namespace-from", - "@babel/plugin-proposal-numeric-separator", - "@babel/plugin-proposal-throw-expressions", - "@babel/plugin-proposal-export-default-from", - "@babel/plugin-proposal-logical-assignment-operators", - "@babel/plugin-proposal-optional-chaining", - [ - "@babel/plugin-proposal-pipeline-operator", - { - "proposal": "minimal" - } - ], - "@babel/plugin-proposal-nullish-coalescing-operator", - "@babel/plugin-proposal-do-expressions", - "@babel/plugin-proposal-function-bind" - ] -} diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..e3486df --- /dev/null +++ b/.eslintignore @@ -0,0 +1,3 @@ +/dist/ +/dist_demo/ +/package/dist/ diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..3c50247 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,38 @@ +module.exports = { + root: true, + extends: [ + // https://eslint.vuejs.org/user-guide/#installation + // add more generic rulesets here, such as: + 'eslint:recommended', + '@vue/standard', + // 'plugin:vue/vue3-recommended', + 'plugin:vue/recommended', // Use this if you are using Vue.js 2.x. + ], + rules: { + 'space-before-function-paren': 'warn', + 'operator-linebreak': 'warn', + 'no-tabs': 'warn', + 'no-new': 'warn', + eqeqeq: 'warn', + indent: 'warn', + quotes: 'warn', + semi: 'warn', + 'vue/multi-word-component-names': 'off', + 'no-var': 'off', + // ... + 'vue/multiline-html-element-content-newline': 'off', + 'vue/first-attribute-linebreak': 'off', + 'vue/max-attributes-per-line': 'off', + 'vue/order-in-components': 'off', + 'vue/attributes-order': 'off', + 'vue/html-indent': 'off', + 'no-irregular-whitespace': 'off', + 'no-mixed-operators': 'off', + 'no-unused-vars': 'off', + 'prefer-const': 'off', + 'comma-dangle': 'off', + 'max-len': 'off', + 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', + 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off' + } +} diff --git a/.gitignore b/.gitignore index 78b3be1..ed14191 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ .DS_Store node_modules*/ dist/ +dist_demo/ +package/ +*.tgz npm-debug.log yarn-error.log diff --git a/README.md b/README.md index 50db045..88815da 100644 --- a/README.md +++ b/README.md @@ -12,31 +12,19 @@ Live Demo & Documentation: https://fritx.github.io/vue-at - [x] Plain-text based, no jQuery, no extra nodes - [x] Content-Editable / Textarea - [x] Avatars, custom templates -- [x] Vue3 / Vue2 / Vue1 +- [x] Vite / Vue3 / Vue2 / Vue1 - [x] Vuetify / Element UI / Element Plus -- [ ] Vue-CLI migration -- [ ] Vite migration +- [x] CommonJS / UMD Support See also: [react-at](https://github.com/fritx/react-at) -## Motivation - -[At.js](https://github.com/ichord/At.js) is awesome, but: - -- It is based on jQuery and jQuery-Caret. -- It introduces extra node wrappers. -- It could be unstable on content edit/copy/paste. - -Finally I ended up creating this. - for Vue3, read [this one](https://github.com/fritx/vue-at/tree/vue3#readme) instead. ```plain -npm i vue-at@next # for Vue3 (branch wip-vue3) - -npm i vue-at@2.x # for Vue2 <---- -npm i vue-at@1.x # for Vue1 (branch vue1-legacy) -npm i vue1-at # for Vue1 (branch vue1-new) +npm i -S vue-at@next # for Vue3 (branch vue3) +npm i -S vue-at@2.x # for Vue2 (branch vue2) +npm i -S vue-at@1.x # for Vue1 (branch vue1-legacy) +npm i -S vue1-at # for Vue1 (branch vue1-new) ``` ```vue @@ -44,13 +32,17 @@ npm i vue1-at # for Vue1 (branch vue1-new)
+ + + +``` - +## UMD Also Supported + +```html + + + + + + + + + + +
+ +
+
+ + + +
+ ``` ## Using V-Model (Recommended) @@ -74,35 +89,11 @@ With Textarea, `v-model` should be bound in ` ``` -## Textarea - -```vue - - - -``` - -```plain -npm i -S textarea-caret # also, for textarea -``` - ## Custom Templates ### Custom List @@ -176,7 +167,7 @@ This gives you the option of changing the style of inserted tagged items. It is ``` -### Element-UI el-input +### Element UI / Element-Plus el-input ```vue diff --git a/build/ViteSingleCssPlugin.js b/build/ViteSingleCssPlugin.js new file mode 100644 index 0000000..09bd4fd --- /dev/null +++ b/build/ViteSingleCssPlugin.js @@ -0,0 +1,47 @@ +// supports vite css.extract=false. +// credits: @ruofee +// https://github.com/vitejs/vite/issues/4345#issuecomment-1073734133 +// https://github.com/ruofee/vue-dynamic-form-component/blob/vite/build/ViteSingleCssPlugin.js + +let packageNames = [] +let viteConfig +let IIFEcss + +// __notice__: this is for package.json without `type=module` +// https://vitejs.dev/guide/build.html#library-mode +// https://nodejs.org/api/packages.html#type +let extMap = { + cjs: '.js', + es: '.mjs', + umd: '.umd.js', +} + +// 将 css 打包到 js 文件中 +export default function () { + return { + apply: 'build', + enforce: 'post', + name: 'pack-css', + configResolved (config) { + viteConfig = config + packageNames = viteConfig.build.lib.formats.map(format => { + return viteConfig.build.lib.fileName + extMap[format] + }) + }, + generateBundle (_, bundle) { + const cssFileName = 'style.css' + const { [cssFileName]: cssBundle } = bundle + if (cssBundle) { + IIFEcss = `(function() {try {var elementStyle = document.createElement('style');elementStyle.textContent = ${JSON.stringify( + cssBundle.source + )};document.head.appendChild(elementStyle);} catch(error) {console.error(error, 'unable to concat style inside the bundled file')}})()` + delete bundle[cssFileName] + } + packageNames.forEach(packageName => { + if (bundle[packageName]) { + bundle[packageName].code += ';;' + IIFEcss + ';;' + } + }) + } + } +} diff --git a/index.html b/index.html index 93f6041..0ff0e55 100644 --- a/index.html +++ b/index.html @@ -1,12 +1,13 @@ - - - vue-at + + + + Demo: vue-at
- + diff --git a/package.json b/package.json index f7c3c0b..3099fb3 100644 --- a/package.json +++ b/package.json @@ -5,60 +5,43 @@ "author": "Fritz Lin ", "repository": "https://github.com/fritx/vue-at", "scripts": { - "dev": "webpack serve --config webpack/demo --open --inline --hot --mode development", - "demo": "webpack --config webpack/demo --progress --mode development", - "build": "webpack --config webpack/prod --progress --mode production", + "lint:fix": "eslint --ext .js,.vue --ignore-path .gitignore --fix src", + "lint": "eslint --ext .js,.vue --ignore-path .gitignore src", + "dev": "vite --force --config vite.demo.config.js", + "dev:local-testing": "cross-env LOCAL_TESTING=1 vite --force --config vite.demo.config.js", + "demo": "vite build --base ./ --config vite.demo.config.js", + "build": "shx rm -rf dist && run-s build:at build:at-ta && shx rm -rf dist/demo", + "build:at": "vite build --config vite.config.1.js", + "build:at-ta": "vite build --config vite.config.2.js", "prepublish": "npm run build" }, - "main": "dist/vue-at.js", + "main": "dist/vue-at", "files": [ "dist" ], "engines": { "node": ">= 14.x" }, + "dependencies": { + "textarea-caret": "^3.1.0" + }, "peerDependencies": { "vue": "2.x" }, "devDependencies": { - "@babel/core": "^7.18.9", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-decorators": "^7.18.9", - "@babel/plugin-proposal-do-expressions": "^7.18.6", - "@babel/plugin-proposal-export-default-from": "^7.18.9", - "@babel/plugin-proposal-export-namespace-from": "^7.18.9", - "@babel/plugin-proposal-function-bind": "^7.18.9", - "@babel/plugin-proposal-function-sent": "^7.18.6", - "@babel/plugin-proposal-json-strings": "^7.18.6", - "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", - "@babel/plugin-proposal-numeric-separator": "^7.18.6", - "@babel/plugin-proposal-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-pipeline-operator": "^7.18.9", - "@babel/plugin-proposal-throw-expressions": "^7.18.6", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/preset-env": "^7.18.9", - "babel-loader": "^8.2.5", - "babel-preset-minify": "^0.5.2", + "@vue/eslint-config-standard": "^8.0.1", "cross-env": "^7.0.3", - "css-loader": "^0.28.11", "element-ui": "^2.15.9", - "file-loader": "^6.2.0", + "eslint": "^8.21.0", + "eslint-plugin-vue": "^9.3.0", + "lodash": "^4.17.21", + "npm-run-all": "^4.1.5", "sass": "^1.53.0", - "sass-loader": "^13.0.2", - "style-loader": "^3.3.1", - "terser-webpack-plugin": "^5.3.3", - "textarea-caret": "^3.1.0", - "url-loader": "^4.1.1", + "shx": "^0.3.4", + "vite": "^3.0.5", + "vite-plugin-vue2": "^2.0.2", "vue": "^2.7.8", - "vue-hot-reload-api": "^2.3.4", - "vue-loader": "^14.2.4", - "vue-style-loader": "^4.1.3", - "vue-template-compiler": "^2.7.8", - "vuetify": "^2.6.7", - "webpack": "^5.73.0", - "webpack-cli": "^4.10.0", - "webpack-dev-server": "^3.11.0" + "vue-template-compiler": "^2.7.10", + "vuetify": "^2.6.7" } } diff --git a/static/awesome.svg b/public/demo/awesome.svg similarity index 100% rename from static/awesome.svg rename to public/demo/awesome.svg diff --git a/static/electron.svg b/public/demo/electron.svg similarity index 100% rename from static/electron.svg rename to public/demo/electron.svg diff --git a/public/demo/vite.svg b/public/demo/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/public/demo/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/App.vue b/src/App.vue index d398abd..ebce691 100644 --- a/src/App.vue +++ b/src/App.vue @@ -68,20 +68,8 @@ + + + +
+ +
+
+ + + +
+ + diff --git a/vite.config.0.js b/vite.config.0.js new file mode 100644 index 0000000..eacbaec --- /dev/null +++ b/vite.config.0.js @@ -0,0 +1,35 @@ +import ViteSingleCssPlugin from './build/ViteSingleCssPlugin' + +// https://vueschool.io/articles/vuejs-tutorials/how-to-migrate-from-vue-cli-to-vite/ +// vue3 +// import vue from '@vitejs/plugin-vue' +// vue2 +import { createVuePlugin as vue } from 'vite-plugin-vue2' + +export default { + plugins: [ + vue(), + // supports vite css.extract=false. + // credits: @ruofee + // https://github.com/vitejs/vite/issues/4345#issuecomment-1073734133 + // https://github.com/ruofee/vue-dynamic-form-component/blob/vite/build/ViteSingleCssPlugin.js + ViteSingleCssPlugin() + ], + build: { + emptyOutDir: false, + sourcemap: true, + target: 'es2015', + rollupOptions: { + // make sure to externalize deps that shouldn't be bundled + // into your library + external: ['vue'], + output: { + // Provide global variables to use in the UMD build + // for externalized deps + globals: { + vue: 'Vue' + } + } + } + } +} diff --git a/vite.config.1.js b/vite.config.1.js new file mode 100644 index 0000000..4d32425 --- /dev/null +++ b/vite.config.1.js @@ -0,0 +1,16 @@ +import { defineConfig } from 'vite' +import { resolve } from 'path' +import commonConfig from './vite.config.0' + +export default defineConfig({ + ...commonConfig, + build: { + ...commonConfig.build, + lib: { + entry: resolve(__dirname, 'src/At.vue'), + name: 'At', + fileName: 'vue-at', + formats: ['cjs', 'umd'] + } + } +}) diff --git a/vite.config.2.js b/vite.config.2.js new file mode 100644 index 0000000..640cdc0 --- /dev/null +++ b/vite.config.2.js @@ -0,0 +1,16 @@ +import { defineConfig } from 'vite' +import { resolve } from 'path' +import commonConfig from './vite.config.0' + +export default defineConfig({ + ...commonConfig, + build: { + ...commonConfig.build, + lib: { + entry: resolve(__dirname, 'src/AtTextarea.vue'), + name: 'AtTextarea', + fileName: 'vue-at-textarea', + formats: ['cjs', 'umd'] + } + } +}) diff --git a/vite.demo.config.js b/vite.demo.config.js new file mode 100644 index 0000000..f97fcac --- /dev/null +++ b/vite.demo.config.js @@ -0,0 +1,62 @@ +import { defineConfig } from 'vite' +import { resolve } from 'path' +import { merge } from 'lodash' + +// https://vueschool.io/articles/vuejs-tutorials/how-to-migrate-from-vue-cli-to-vite/ +// vue3 +// import vue from '@vitejs/plugin-vue' +// vue2 +import { createVuePlugin as vue } from 'vite-plugin-vue2' + +let options = { + plugins: [vue()], + resolve: { + alias: { + // fix: vutify [Bug Report] "$attrs is readonly" and "$listeners is readonly" console messages. #4068 + // found in ---> + // Vite/Rollup solution + // https://github.com/vuetifyjs/vuetify/discussions/4068#discussioncomment-1357093 + vue: resolve(__dirname, './node_modules/vue/dist/vue.runtime.esm.js'), + + // opt.1 developing src + 'vue-at/dist/vue-at-textarea': resolve(__dirname, 'src/AtTextarea.vue'), + 'vue-at': resolve(__dirname, 'src/At.vue'), + + // opt.2 testing dist + // 'vue-at/dist/vue-at-textarea': resolve(__dirname, 'dist/vue-at-textarea'), + // 'vue-at': resolve(__dirname, 'dist/vue-at.js'), + + // opt.3 testing npm_pack + // 'vue-at/dist/vue-at-textarea': resolve(__dirname, 'package/dist/vue-at-textarea'), + // 'vue-at': resolve(__dirname, 'package') + '/', // trailing slash is required here + }, + }, + build: { + outDir: resolve(__dirname, 'dist_demo') + } +} + +if (process.env.LOCAL_TESTING) { + merge(options, { + // Cannot read property 'isCE' of null in remote component with slot using Module Federation #4344 + // https://github.com/vuejs/core/issues/4344#issuecomment-1023220225 + resolve: { + dedupe: ['vue'], + }, + + // Vite - Dependency Pre-Bundling + // Monorepos and Linked Dependencies + // https://vitejs.dev/guide/dep-pre-bundling.html#monorepos-and-linked-dependencies + optimizeDeps: { + include: ['vue-at', 'vue-at/dist/vue-at-textarea'] + }, + build: { + commonjsOptions: { + include: [/vue-at/, /node_modules/] + } + } + }) +} + +// https://vitejs.dev/config/ +export default defineConfig(options) diff --git a/webpack/base.js b/webpack/base.js deleted file mode 100644 index cc1eb0a..0000000 --- a/webpack/base.js +++ /dev/null @@ -1,64 +0,0 @@ -var path = require('path') -var webpack = require('webpack') - -module.exports = { - module: { - rules: [ - { - test: /\.vue$/, - use: ['vue-loader'] - }, - { - test: /\.scss$/, - use: [ - 'vue-style-loader', - 'css-loader', - 'sass-loader', - ] - }, - { - test: /\.sass$/, - use: [ - 'vue-style-loader', - 'css-loader', - 'sass-loader?indentedSyntax', - ] - }, - { - test: /\.js$/, - use: ['babel-loader'], - exclude: [ - /node_modules/, - path.resolve(__dirname, '../dist'), - path.resolve(__dirname, '../package'), - ] - }, - { - test: /\.css$/, - use: ['style-loader', 'css-loader'], - }, - { - test: /\.(ttf|woff)$/, - use: ['url-loader'], - }, - { - test: /\.(png|jpg|gif|svg)$/, - use: [ - { - loader: 'file-loader', - options: { - name: '[name].[ext]?[hash]' - } - } - ] - } - ] - }, - resolve: { - alias: { - 'vue$': 'vue/dist/vue.esm.js' // 'vue/dist/vue.common.js' webpack - // https://vuejs.org/v2/guide/installation.html#Standalone-vs-Runtime-only-Build - // 'vue$': 'vue/dist/vue.common.js' - } - } -} diff --git a/webpack/demo.js b/webpack/demo.js deleted file mode 100644 index c640678..0000000 --- a/webpack/demo.js +++ /dev/null @@ -1,35 +0,0 @@ -var path = require('path') -var webpack = require('webpack') -var base = require('./base') -var config = module.exports = Object.assign({}, base) - -const isProd = process.env.NODE_ENV === 'production' -console.log('isProd', isProd) - -Object.assign(config, { - entry: './src/main.js', - output: { - path: path.resolve(__dirname, '../dist'), - publicPath: '/dist/', - filename: 'demo.js' - }, - devServer: { - historyApiFallback: true, - noInfo: true - }, - devtool: isProd ? false : 'eval-source-map', - plugins: [ - new webpack.DefinePlugin({ - 'process.env': { - NODE_ENV: '"development"' - } - }), - isProd - ? new webpack.optimize.UglifyJsPlugin({ - compress: { - warnings: false - } - }) - : { apply: () => {} } - ] -}) diff --git a/webpack/prod.js b/webpack/prod.js deleted file mode 100644 index bb9364f..0000000 --- a/webpack/prod.js +++ /dev/null @@ -1,44 +0,0 @@ -const TerserPlugin = require("terser-webpack-plugin"); -var path = require('path') -var webpack = require('webpack') -var base = require('./base') -var config = module.exports = Object.assign({}, base) - -Object.assign(config, { - entry: { - 'vue-at': './src/At.vue', - 'vue-at-textarea': './src/AtTextarea.vue' - }, - output: { - path: path.resolve(__dirname, '../dist'), - filename: '[name].js', - libraryTarget: 'commonjs2' - }, - devtool: 'source-map', - plugins: [ - new webpack.DefinePlugin({ - 'process.env': { - NODE_ENV: '"production"' - } - }), - new webpack.LoaderOptionsPlugin({ - minimize: true - }), - new webpack.ExternalsPlugin('commonjs2', [ - 'vue', - 'textarea-caret' - ]), - ], - optimization: { - minimize: true, - minimizer: [ - new TerserPlugin({ - parallel: true, - terserOptions: { - compress: true, - sourceMap: true - } - }), - ] - } -})