├── .eslintignore ├── .npmrc ├── tsconfig.json ├── playground ├── package.json ├── components │ ├── icon-card.vue │ ├── header-menu.vue │ ├── table-card.vue │ ├── tree-card.vue │ ├── other-card.vue │ ├── navigation-card.vue │ ├── data-card.vue │ └── form-card.vue ├── utils.ts ├── nuxt.config.ts └── app.vue ├── .eslintrc ├── .nuxtrc ├── .editorconfig ├── src ├── core │ ├── index.ts │ ├── cache.ts │ ├── directives.ts │ ├── themes.ts │ ├── injection.ts │ ├── methods.ts │ ├── styles.ts │ ├── globalConfig.ts │ ├── localePlugn.ts │ ├── teleports.ts │ ├── imports.ts │ ├── components.ts │ ├── options.ts │ ├── themeChalk.ts │ └── transformPlugin.ts ├── utils.ts ├── module.ts ├── config.ts └── types.ts ├── .vscode └── launch.json ├── .gitignore ├── .github └── workflows │ ├── unit-test.yml │ └── release.yml ├── LICENSE ├── package.json └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | node-linker=hoisted 3 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./playground/.nuxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "playground" 4 | } 5 | -------------------------------------------------------------------------------- /playground/components/icon-card.vue: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@nuxtjs/eslint-config-typescript" 4 | ], 5 | "rules": { 6 | "@typescript-eslint/no-unused-vars": "off" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /playground/utils.ts: -------------------------------------------------------------------------------- 1 | import type { MessageParams } from 'element-plus' 2 | 3 | export function $message (options?: MessageParams) { 4 | return ElMessage(options) 5 | } 6 | -------------------------------------------------------------------------------- /.nuxtrc: -------------------------------------------------------------------------------- 1 | imports.autoImport=true 2 | typescript.includeWorkspace=true 3 | 4 | # enable TypeScript bundler module resolution - https://www.typescriptlang.org/docs/handbook/modules/reference.html#bundler 5 | experimental.typescriptBundlerResolution=true -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_size = 2 5 | indent_style = space 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /src/core/index.ts: -------------------------------------------------------------------------------- 1 | export * from './cache' 2 | export * from './components' 3 | export * from './directives' 4 | export * from './globalConfig' 5 | export * from './imports' 6 | export * from './injection' 7 | export * from './localePlugn' 8 | export * from './methods' 9 | export * from './options' 10 | export * from './styles' 11 | export * from './teleports' 12 | export * from './themeChalk' 13 | export * from './themes' 14 | export * from './transformPlugin' 15 | -------------------------------------------------------------------------------- /src/core/cache.ts: -------------------------------------------------------------------------------- 1 | import { libraryName } from '../config' 2 | import type { ModuleOptions } from '../types' 3 | import { camelize } from '../utils' 4 | 5 | export function resolveCache (config: ModuleOptions) { 6 | const { defaultLocale } = config 7 | const locale = camelize(defaultLocale) 8 | 9 | return { 10 | filename: `${libraryName}-cache.mjs`, 11 | getContents: () => { 12 | return `export * from '${libraryName}'; 13 | ${defaultLocale !== 'en' 14 | ? `import ${locale} from '${libraryName}/es/locale/lang/${defaultLocale}.mjs'; 15 | export { ${locale} };` 16 | : ''}` 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/core/directives.ts: -------------------------------------------------------------------------------- 1 | import { toArray } from '../utils' 2 | import type { ModuleOptions } from '../types' 3 | import { getComponentPath, getStyleDir } from './index' 4 | 5 | export function resolveDirectives ( 6 | config: ModuleOptions, 7 | name: string 8 | ): undefined | [name: string, path: string, style?: string] { 9 | const { directives } = config 10 | 11 | if (!directives[name]) { return undefined } 12 | 13 | const [directive, styleName] = toArray(directives[name]) 14 | const path = getComponentPath(styleName ?? directive) 15 | const style = styleName && getStyleDir(config, styleName) 16 | 17 | return [directive, path, style] 18 | } 19 | -------------------------------------------------------------------------------- /src/core/themes.ts: -------------------------------------------------------------------------------- 1 | import { useNuxt } from '@nuxt/kit' 2 | import { libraryName } from '../config' 3 | import { resolvePath } from '../utils' 4 | import type { ModuleOptions } from '../types' 5 | 6 | export function resolveThemes (config: ModuleOptions) { 7 | const nuxt = useNuxt() 8 | const { themes, importStyle } = config 9 | const allThemes = new Set(themes) 10 | 11 | if (importStyle === false) { 12 | return 13 | } 14 | 15 | allThemes.forEach(async (item) => { 16 | const isScss = importStyle === 'scss' 17 | const theme = await resolvePath(`${libraryName}/theme-chalk${isScss ? '/src' : ''}/${item}/css-vars.${isScss ? 'scss' : 'css'}`) 18 | 19 | nuxt.options.css.push(theme) 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch Chrome", 6 | "request": "launch", 7 | "type": "chrome", 8 | "url": "http://localhost:3000", 9 | "webRoot": "${workspaceFolder}" 10 | }, 11 | { 12 | "name": "Launch Edge", 13 | "request": "launch", 14 | "type": "msedge", 15 | "url": "http://localhost:3000", 16 | "webRoot": "${workspaceFolder}" 17 | }, 18 | { 19 | "name": "Run and debug", 20 | "type": "node", 21 | "request": "launch", 22 | "cwd": "${workspaceFolder}", 23 | "runtimeExecutable": "pnpm", 24 | "runtimeArgs": ["dev", "--preserve-symlinks"] 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/core/injection.ts: -------------------------------------------------------------------------------- 1 | import { libraryName } from '../config' 2 | import type { ModuleOptions } from '../types' 3 | 4 | /** Inject some additional configuration into Vue at runtime */ 5 | export function resolveInjection (config: ModuleOptions) { 6 | const { injectionID, injectionZIndex } = config 7 | 8 | return { 9 | filename: `${libraryName}-injection.plugin.mjs`, 10 | getContents: () => { 11 | return `import { defineNuxtPlugin, ID_INJECTION_KEY, ZINDEX_INJECTION_KEY } from '#imports'; 12 | 13 | export default defineNuxtPlugin(nuxtApp => { 14 | nuxtApp.vueApp 15 | .provide(ID_INJECTION_KEY, ${JSON.stringify(injectionID)}) 16 | .provide(ZINDEX_INJECTION_KEY, ${JSON.stringify(injectionZIndex)}); 17 | }) 18 | ` 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | 4 | # Logs 5 | *.log* 6 | 7 | # Temp directories 8 | .temp 9 | .tmp 10 | .cache 11 | 12 | # Yarn 13 | **/.yarn/cache 14 | **/.yarn/*state* 15 | 16 | # Generated dirs 17 | dist 18 | 19 | # Nuxt 20 | .nuxt 21 | .output 22 | .vercel_build_output 23 | .build-* 24 | .env 25 | .netlify 26 | 27 | # Env 28 | .env 29 | 30 | # Testing 31 | reports 32 | coverage 33 | *.lcov 34 | .nyc_output 35 | 36 | # VSCode 37 | .vscode/* 38 | !.vscode/settings.json 39 | !.vscode/tasks.json 40 | !.vscode/launch.json 41 | !.vscode/extensions.json 42 | !.vscode/*.code-snippets 43 | 44 | # Intellij idea 45 | *.iml 46 | .idea 47 | 48 | # OSX 49 | .DS_Store 50 | .AppleDouble 51 | .LSOverride 52 | .AppleDB 53 | .AppleDesktop 54 | Network Trash Folder 55 | Temporary Items 56 | .apdisk 57 | 58 | package-lock.json 59 | yarn.lock 60 | -------------------------------------------------------------------------------- /src/core/methods.ts: -------------------------------------------------------------------------------- 1 | import { libraryName } from '../config' 2 | import type { ModuleOptions } from '../types' 3 | 4 | /** 5 | * NOTE: Due to some situations where the value cannot be obtained when using getCurrentInstance in the setup function, it is currently not possible to automatically inject the context. 6 | * Now let the user configure it first, and then look for a more suitable way in the future. 7 | */ 8 | export function resolveMethods (config: ModuleOptions) { 9 | const { installMethods } = config 10 | 11 | return { 12 | filename: `${libraryName}-methods.plugin.mjs`, 13 | getContents: () => { 14 | return `import { defineNuxtPlugin, ${installMethods.join(',')} } from '#imports'; 15 | 16 | export default defineNuxtPlugin(nuxtApp => { 17 | ${installMethods.reduce((all, name) => `${all}.use(${name})`, 'nuxtApp.vueApp')}; 18 | }) 19 | ` 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/core/styles.ts: -------------------------------------------------------------------------------- 1 | import { allMethods, libraryName } from '../config' 2 | import { hyphenate } from '../utils' 3 | import type { ModuleOptions } from '../types' 4 | 5 | export function getStyleDir (config: ModuleOptions, name: string) { 6 | if (config.importStyle === false) { 7 | return undefined 8 | } 9 | const dir = hyphenate(name.slice(2)) 10 | const type = config.importStyle === 'scss' ? 'index' : 'css' 11 | 12 | return `${libraryName}/es/components/${dir}/style/${type}.mjs` 13 | } 14 | 15 | export function resolveStyles (config: ModuleOptions, name: string) { 16 | const { components, noStylesComponents } = config 17 | const allComponents = [...components, ...allMethods] 18 | 19 | if (!allComponents.includes(name) || noStylesComponents.includes(name)) { 20 | return undefined 21 | } 22 | 23 | return /^El[A-Z]/.test(name) ? getStyleDir(config, name) : undefined 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/unit-test.yml: -------------------------------------------------------------------------------- 1 | name: Unit Test 2 | 3 | on: 4 | pull_request: 5 | branches: [main] 6 | 7 | jobs: 8 | test: 9 | strategy: 10 | matrix: 11 | os: [ubuntu-latest, windows-latest] 12 | node-version: [18, 20] 13 | fail-fast: false 14 | 15 | runs-on: ${{ matrix.os }} 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - uses: pnpm/action-setup@v4 21 | name: Install pnpm 22 | with: 23 | version: latest 24 | 25 | - uses: actions/setup-node@v4 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | cache: pnpm 29 | 30 | - name: Install 31 | run: pnpm i --frozen-lockfile 32 | 33 | - name: Build 34 | run: pnpm build 35 | 36 | - name: Lint 37 | run: pnpm lint:test 38 | 39 | - name: Build playground 40 | run: pnpm dev:build 41 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v**' 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | with: 14 | fetch-depth: 0 15 | 16 | - name: Setup node 17 | uses: actions/setup-node@v4 18 | with: 19 | node-version: latest 20 | registry-url: https://registry.npmjs.org/ 21 | 22 | - name: Setup pnpm 23 | uses: pnpm/action-setup@v4 24 | with: 25 | version: latest 26 | 27 | - name: Install 28 | run: pnpm i --frozen-lockfile 29 | 30 | - name: Build 31 | run: pnpm build 32 | 33 | - name: Publish 34 | run: pnpm publish --access public --no-git-checks 35 | env: 36 | NODE_AUTH_TOKEN: ${{secrets.NPM_PUBLISH_TOKEN}} 37 | 38 | - run: npx changelogithub 39 | env: 40 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 41 | -------------------------------------------------------------------------------- /src/core/globalConfig.ts: -------------------------------------------------------------------------------- 1 | import { libraryName } from '../config' 2 | import type { ModuleOptions } from '../types' 3 | import { camelize, resolveComponentPath } from '../utils' 4 | 5 | export function resolveGlobalConfig (config: ModuleOptions) { 6 | const { globalConfig, cache, defaultLocale } = config 7 | const needLocale = !!(cache && defaultLocale !== 'en') 8 | const locale = camelize(defaultLocale) 9 | let provideConfig = JSON.stringify(globalConfig) 10 | 11 | if (needLocale) { 12 | provideConfig = JSON.stringify(Object.assign({}, globalConfig, { locale })).replace(`"${locale}"`, locale) 13 | } 14 | 15 | return { 16 | filename: `${libraryName}-globalConfig.plugin.mjs`, 17 | getContents: async () => { 18 | return `import { defineNuxtPlugin, provideGlobalConfig } from '#imports'; 19 | ${needLocale ? `import { ${locale} } from '${await resolveComponentPath('', cache)}';\n` : ''} 20 | export default defineNuxtPlugin(nuxtApp => { 21 | provideGlobalConfig(${provideConfig}, nuxtApp.vueApp, true); 22 | })` 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/core/localePlugn.ts: -------------------------------------------------------------------------------- 1 | import { createUnplugin } from 'unplugin' 2 | import MagicString from 'magic-string' 3 | import type { NuxtOptions } from '@nuxt/schema' 4 | import { libraryName } from '../config' 5 | 6 | interface LocalePluginOptions { 7 | sourcemap?: NuxtOptions['sourcemap']['client'] 8 | locale?: string 9 | } 10 | 11 | export const localePlugin = createUnplugin((options: LocalePluginOptions) => { 12 | return { 13 | name: `${libraryName}:locale`, 14 | enforce: 'pre', 15 | transformInclude (id) { 16 | const regExp = new RegExp(`${libraryName}/es/hooks/use-locale/index`) 17 | return !!id.match(regExp) 18 | }, 19 | transform (code, id) { 20 | const s = new MagicString(code) 21 | 22 | s.replace('/locale/lang/en', `/locale/lang/${options.locale}`) 23 | 24 | if (s.hasChanged()) { 25 | return { 26 | code: s.toString(), 27 | map: options.sourcemap 28 | ? s.generateMap({ source: id, includeContent: true }) 29 | : undefined 30 | } 31 | } 32 | } 33 | } 34 | }) 35 | -------------------------------------------------------------------------------- /src/core/teleports.ts: -------------------------------------------------------------------------------- 1 | import { libraryName } from '../config' 2 | import type { ModuleOptions } from '../types' 3 | 4 | export function resolveTeleports (config: ModuleOptions) { 5 | const { namespace, appendTo } = config 6 | const defaultId = `#${namespace}-popper-container-` 7 | 8 | return { 9 | filename: `${libraryName}-teleports.plugin.mjs`, 10 | getContents: () => { 11 | return `import { defineNuxtPlugin } from '#imports' 12 | 13 | export default defineNuxtPlugin((nuxtApp) => { 14 | nuxtApp.hook('app:rendered', (ctx) => { 15 | if (ctx.ssrContext?.teleports) { 16 | ctx.ssrContext.teleports = renderTeleports(ctx.ssrContext.teleports) 17 | } 18 | }) 19 | }) 20 | 21 | function renderTeleports (teleports) { 22 | const body = Object.entries(teleports).reduce((all, [key, value]) => { 23 | if (key.startsWith('${defaultId}') || ${JSON.stringify(appendTo)}.includes(key)) { 24 | return \`\${all}
\${value}
\` 25 | } 26 | return all 27 | }, teleports.body || '') 28 | return { ...teleports, body } 29 | } 30 | ` 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-PRESENT Element Plus 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 | -------------------------------------------------------------------------------- /playground/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineNuxtConfig } from 'nuxt/config' 2 | import ElementPlus from '../src/module' 3 | 4 | export default defineNuxtConfig({ 5 | devtools: { enabled: true }, 6 | compatibilityDate: '2024-07-05', 7 | modules: [ElementPlus], 8 | elementPlus: { 9 | cache: true, 10 | importStyle: 'scss', 11 | defaultLocale: 'zh-cn', 12 | imports: [ 13 | ['useLocale', 'es/hooks/use-locale/index.mjs'] 14 | ], 15 | themes: ['dark'], 16 | namespace: 'ep', 17 | injectionID: { prefix: 100, current: 1 }, 18 | globalConfig: { size: 'small', zIndex: 1000, namespace: 'ep' }, 19 | themeChalk: { 20 | $colors: { 21 | primary: { base: 'rgba(107,33,168, 1)' }, 22 | success: { base: 'green' }, 23 | warning: { base: '#f9a23c' }, 24 | danger: { base: '#ff3300' }, 25 | error: { base: '#f56c6c' } 26 | }, 27 | dark: { 28 | $colors: { 29 | primary: { base: 'rgb(242, 216, 22)' } 30 | }, 31 | '$bg-color': { 32 | page: '#0a0a0a', 33 | overlay: '#1d1e1f' 34 | } 35 | } 36 | } 37 | } 38 | }) 39 | -------------------------------------------------------------------------------- /src/core/imports.ts: -------------------------------------------------------------------------------- 1 | import { addImportsSources } from '@nuxt/kit' 2 | import { allMethods, iconLibraryName, libraryName } from '../config' 3 | import { genIconPresets, resolveComponentPath, resolvePath, toArray } from '../utils' 4 | import type { ModuleOptions, PresetImport } from '../types' 5 | import { getComponentPath } from './index' 6 | 7 | function _resolveImports (imports: Set, cache: boolean | undefined) { 8 | imports.forEach(async ([name, path]) => { 9 | addImportsSources({ 10 | from: await resolveComponentPath(path, cache), 11 | imports: toArray(name) 12 | }) 13 | }) 14 | } 15 | 16 | export async function resolveImports (config: ModuleOptions) { 17 | const { imports, icon, cache } = config 18 | const icons = icon !== false ? genIconPresets(icon) : [] 19 | const allImports = new Set(imports) 20 | const allIcons = new Set(icons) 21 | 22 | _resolveImports(allImports, cache) 23 | 24 | addImportsSources({ 25 | from: await resolvePath(iconLibraryName), 26 | imports: [...allIcons] 27 | }) 28 | } 29 | 30 | export function resolveBaseImports (config: ModuleOptions) { 31 | const { baseImports, cache } = config 32 | const methodImports = allMethods.map((name) => { 33 | return [name, getComponentPath(name)] as PresetImport 34 | }) 35 | const allBaseImports = new Set([...baseImports, ...methodImports]) 36 | 37 | _resolveImports(allBaseImports, cache) 38 | } 39 | -------------------------------------------------------------------------------- /src/core/components.ts: -------------------------------------------------------------------------------- 1 | import { addComponent } from '@nuxt/kit' 2 | import { iconLibraryName } from '../config' 3 | import { genIconPresets, toArray, resolvePath, hyphenate, resolveComponentPath } from '../utils' 4 | import type { ModuleOptions } from '../types' 5 | 6 | export function getComponentPath (name: string): string { 7 | const dir = hyphenate(name.slice(2)) 8 | return `es/components/${dir}/index.mjs` 9 | } 10 | 11 | export function resolveComponents (config: ModuleOptions) { 12 | const { components, subComponents, icon, cache } = config 13 | const icons = icon !== false ? genIconPresets(icon, iconLibraryName) : [] 14 | const allComponents = new Set([...components, ...icons]) 15 | const subComponentsMap = Object.fromEntries( 16 | Object.entries(subComponents).reduce((all, [key, values]) => { 17 | values.forEach((item) => { 18 | all.push([item, key]) 19 | }) 20 | return all 21 | }, [] as unknown as [string, any]) 22 | ) 23 | 24 | allComponents.forEach(async (item) => { 25 | const [name, alias, from] = toArray(item) 26 | const componentName = subComponentsMap[name] || name 27 | const filePath = from !== iconLibraryName 28 | ? await resolveComponentPath(getComponentPath(componentName), cache) 29 | : await resolvePath(from) 30 | 31 | addComponent({ 32 | export: name, 33 | name: alias || name, 34 | filePath 35 | }) 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /playground/components/header-menu.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@element-plus/nuxt", 3 | "version": "1.1.4", 4 | "description": "Element Plus module for Nuxt", 5 | "license": "MIT", 6 | "type": "module", 7 | "exports": { 8 | ".": { 9 | "types": "./dist/types.d.ts", 10 | "import": "./dist/module.mjs", 11 | "require": "./dist/module.cjs" 12 | } 13 | }, 14 | "main": "./dist/module.cjs", 15 | "types": "./dist/types.d.ts", 16 | "files": [ 17 | "dist" 18 | ], 19 | "keywords": [ 20 | "element-plus", 21 | "nuxt" 22 | ], 23 | "homepage": "https://github.com/element-plus/element-plus-nuxt#readme", 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/element-plus/element-plus-nuxt.git" 27 | }, 28 | "bugs": { 29 | "url": "https://github.com/element-plus/element-plus-nuxt/issues" 30 | }, 31 | "scripts": { 32 | "build": "npm run dev:prepare && nuxt-module-build build", 33 | "dev": "nuxi dev playground", 34 | "dev:build": "nuxi build playground", 35 | "dev:start": "node playground/.output/server/index.mjs", 36 | "dev:prepare": "nuxt-module-build build --stub && nuxi prepare playground", 37 | "lint": "eslint . --fix --ext .ts,.vue,.js", 38 | "lint:test": "eslint . --ext .ts,.vue,.js --max-warnings 0" 39 | }, 40 | "peerDependencies": { 41 | "@element-plus/icons-vue": ">=0.2.6", 42 | "element-plus": ">=2" 43 | }, 44 | "dependencies": { 45 | "@nuxt/kit": "^3.13.2", 46 | "magic-string": "^0.27.0", 47 | "unplugin": "^1.15.0" 48 | }, 49 | "devDependencies": { 50 | "@element-plus/icons-vue": "^2.3.1", 51 | "@nuxt/module-builder": "^0.5.5", 52 | "@nuxt/schema": "^3.13.2", 53 | "@nuxtjs/eslint-config-typescript": "^12.1.0", 54 | "@types/node": "^22.8.6", 55 | "element-plus": "^2.10.1", 56 | "eslint": "^8.57.1", 57 | "nuxi": "^3.15.0", 58 | "nuxt": "^3.13.2", 59 | "sass": "^1.89.2", 60 | "typescript": "^5.6.3", 61 | "vue": "^3.5.12" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /playground/components/table-card.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 75 | -------------------------------------------------------------------------------- /playground/components/tree-card.vue: -------------------------------------------------------------------------------- 1 | 101 | 102 | 116 | -------------------------------------------------------------------------------- /src/core/options.ts: -------------------------------------------------------------------------------- 1 | import { useNuxt } from '@nuxt/kit' 2 | import { libraryName, optimizeDeps } from '../config' 3 | import { isFunction } from '../utils' 4 | import type { ModuleOptions, Themes } from '../types' 5 | 6 | export function resolveOptions (config: ModuleOptions) { 7 | const { cache, importStyle, namespace, themeChalk } = config 8 | const nuxt = useNuxt() 9 | 10 | nuxt.options.build.transpile.push(libraryName) 11 | nuxt.options.vite.optimizeDeps ||= {} 12 | nuxt.options.vite.optimizeDeps.include ||= [] 13 | nuxt.options.vite.optimizeDeps.include.push(...optimizeDeps) 14 | 15 | if (cache) { 16 | nuxt.options.vite.optimizeDeps.include.push(libraryName) 17 | } else { 18 | nuxt.options.vite.optimizeDeps.exclude ||= [] 19 | nuxt.options.vite.optimizeDeps.exclude.push(libraryName) 20 | } 21 | 22 | nuxt.options.vite.css ||= {} 23 | nuxt.options.vite.css.preprocessorOptions ||= {} 24 | nuxt.options.vite.css.preprocessorOptions.scss ||= {} 25 | nuxt.options.vite.css.preprocessorOptions.scss.api ??= 'modern-compiler' 26 | nuxt.options.webpack.loaders.scss.api ??= 'modern-compiler' 27 | 28 | if (importStyle === 'scss' && themeChalk) { 29 | const files: Array<'namespace' | 'common' | Themes> = [] 30 | const keys = Object.keys(themeChalk) as (keyof typeof themeChalk)[] 31 | const themes = keys.filter((key) => { 32 | return !key.startsWith('$') && themeChalk[key] && Object.keys(themeChalk[key]).length 33 | }) as Themes[] 34 | 35 | if (namespace && namespace !== 'el') { 36 | files.push('namespace') 37 | } 38 | if (keys.some(key => key.startsWith('$'))) { 39 | files.push('common') 40 | } 41 | files.push(...themes) 42 | 43 | const additionalData = files.reduce((all, item) => { 44 | all += `@use "${nuxt.options.buildDir}/${libraryName}-scss-${item}.scss";` 45 | return all 46 | }, '') 47 | 48 | async function genAdditionalData (old: string | Function | undefined, source: string, ...arg: unknown[]) { 49 | const content = isFunction(old) ? await old(source, ...arg) : (old ?? '') + source 50 | return additionalData + content 51 | } 52 | 53 | if (additionalData) { 54 | const oldVite = nuxt.options.vite.css!.preprocessorOptions!.scss.additionalData 55 | nuxt.options.vite.css.preprocessorOptions.scss.additionalData = (source: string, ...arg: unknown[]) => { 56 | return genAdditionalData(oldVite, source, ...arg) 57 | } 58 | 59 | const oldWebpack = nuxt.options.webpack.loaders.scss.additionalData 60 | nuxt.options.webpack.loaders.scss.additionalData = (source: string, ...arg: unknown[]) => { 61 | return genAdditionalData(oldWebpack, source, ...arg) 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/core/themeChalk.ts: -------------------------------------------------------------------------------- 1 | import type { NuxtTemplate } from '@nuxt/schema' 2 | import { libraryName } from '../config' 3 | import { isArray, isObject, resolveComponentPath } from '../utils' 4 | import type { ModuleOptions, ScssChalk, ScssVariables, Themes } from '../types' 5 | 6 | export function resolveThemeChalk (config: ModuleOptions) { 7 | const { themeChalk, namespace } = config 8 | const files: NuxtTemplate[] = [] 9 | const common: ScssChalk = {} 10 | const themes: Themes[] = [] 11 | 12 | if (!themeChalk) { return [] } 13 | 14 | Object.keys(themeChalk).forEach((key) => { 15 | if (key.startsWith('$')) { 16 | const _key = key as keyof ScssChalk 17 | common[_key] = themeChalk[_key] 18 | } else { 19 | themes.push(key as Themes) 20 | } 21 | }) 22 | 23 | if (namespace && namespace !== 'el') { 24 | files.push(genNamespaceFile()) 25 | } 26 | 27 | if (Object.keys(common).length) { 28 | files.push(genThemeChalkFile('common', common)) 29 | } 30 | 31 | themes.forEach((type) => { 32 | const config = themeChalk[type] 33 | if (config && Object.keys(config).length) { 34 | files.push(genThemeChalkFile(type, config)) 35 | } 36 | }) 37 | 38 | function genNamespaceFile () : NuxtTemplate { 39 | return { 40 | filename: `${libraryName}-scss-namespace.scss`, 41 | write: true, 42 | getContents: async () => { 43 | return `@forward '${await resolveComponentPath('theme-chalk/src/mixins/config.scss', false)}' with ( 44 | $namespace: '${namespace}' 45 | );` 46 | } 47 | } 48 | } 49 | 50 | function genThemeChalkFile (type: Themes | 'common', config: ScssChalk): NuxtTemplate { 51 | return { 52 | filename: `${libraryName}-scss-${type}.scss`, 53 | write: true, 54 | getContents: async () => { 55 | return `@forward '${await resolveComponentPath(`theme-chalk/src/${type}/var.scss`, false)}' with ( 56 | ${genScssVariables(config)} 57 | );` 58 | } 59 | } 60 | } 61 | 62 | function genScssVariables (config: ScssChalk): string { 63 | function genValue (value: string | string[] | ScssVariables>): string { 64 | if (isArray(value)) { 65 | return `(${value.join(', ')})` 66 | } else if (isObject(value)) { 67 | return `(${Object.entries(value).reduce((all, [k, v]) => { 68 | if (!v) { return all } 69 | all.push(`'${k}': ${genValue(v)}`) 70 | return all 71 | }, [] as string[]).join(', ')})` 72 | } else { 73 | return value 74 | } 75 | } 76 | 77 | return Object.entries(config).reduce((all, [key, value]) => { 78 | if (!value) { return all } 79 | all.push(` ${key}: ${genValue(value)}`) 80 | return all 81 | }, [] as string[]).join(',\n') 82 | } 83 | 84 | return files 85 | } 86 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import type { Component } from 'vue' 2 | import { createResolver } from '@nuxt/kit' 3 | import type { NuxtConfigLayer } from '@nuxt/schema' 4 | import { allIcons, libraryName } from './config' 5 | import type { PresetComponent } from './types' 6 | 7 | export function resolvePath (path: string): Promise { 8 | const { resolvePath } = createResolver(import.meta.url) 9 | return resolvePath(path) 10 | } 11 | 12 | export async function resolveComponentPath (path: string, cache: boolean | undefined): Promise { 13 | if (cache) { 14 | return `#build/${libraryName}-cache.mjs` 15 | } 16 | 17 | return await resolvePath(`${libraryName}/${path}`) 18 | } 19 | 20 | export function getLayersDir (layers: NuxtConfigLayer[]) { 21 | const list = [] 22 | 23 | for (const layer of layers) { 24 | const srcDir = layer.config.srcDir || layer.cwd 25 | if (srcDir.includes('node_modules') && layer.config.elementPlus?.importStyle !== false) { 26 | list.push(srcDir) 27 | } 28 | } 29 | 30 | return list 31 | } 32 | 33 | export function isObject (value: any): value is Record { 34 | return typeof value === 'object' && value !== null && !isArray(value) 35 | } 36 | 37 | export function isFunction (value: any): value is Function { 38 | return typeof value === 'function' 39 | } 40 | 41 | export function isArray (value: any): value is any[] { 42 | return Array.isArray(value) 43 | } 44 | 45 | export function isVueComponent (value: any): value is Component { 46 | return typeof value === 'object' && value.name && (value.props || value.emits || value.setup || value.render) 47 | } 48 | 49 | export function toArray ( 50 | value: T 51 | ): T extends any[] ? T : T[] { 52 | return (isArray(value) ? value : [value]) as any 53 | } 54 | 55 | export function toRegExp (arr: string[], flags?: string): RegExp { 56 | return new RegExp(`\\b(${arr.join('|')})\\b`, flags) 57 | } 58 | 59 | export async function genLibraryImport ([name, as, from]: Required>, cache: boolean | undefined): Promise { 60 | const fromPath = await resolveComponentPath(from, cache) 61 | return `import { ${name} as ${as} } from '${fromPath}';\n` 62 | } 63 | 64 | export async function genSideEffectsImport (from: string): Promise { 65 | const fromPath = await resolvePath(from) 66 | return `import '${fromPath}';\n` 67 | } 68 | 69 | export function genIconPresets (prefix: string, from?: string): Exclude[] { 70 | return allIcons.map((name) => { 71 | return [name, `${prefix}${name}`, from] as Exclude 72 | }) 73 | } 74 | 75 | export function camelize (value: string): string { 76 | return value.replace(/(^|-)(\w)/g, (a, b, c) => c.toUpperCase()) 77 | } 78 | 79 | export function hyphenate (value: string): string { 80 | return value.replace(/\B([A-Z])/g, '-$1').toLowerCase() 81 | } 82 | -------------------------------------------------------------------------------- /src/core/transformPlugin.ts: -------------------------------------------------------------------------------- 1 | import { createUnplugin } from 'unplugin' 2 | import MagicString from 'magic-string' 3 | import type { NuxtOptions } from '@nuxt/schema' 4 | import { allMethods, libraryName } from '../config' 5 | import { 6 | camelize, 7 | genLibraryImport, 8 | genSideEffectsImport, 9 | toRegExp 10 | } from '../utils' 11 | import type { PresetComponent, TransformOptions } from '../types' 12 | 13 | interface PluginOptions extends TransformOptions { 14 | layers: string[] 15 | cache: boolean | undefined 16 | sourcemap?: NuxtOptions['sourcemap']['client'] 17 | transformStyles: (name: string) => undefined | string 18 | transformDirectives: (name: string) => undefined | [name: string, path: string, style?: string] 19 | } 20 | 21 | const componentsRegExp = /(?<=[ (])_?resolveComponent\(\s*["'](lazy-|Lazy)?([^'"]*?)["'][\s,]*[^)]*\)/g 22 | const directivesRegExp = /(?<=[ (])_?resolveDirective\(\s*["']([^'"]*?)["'][\s,]*[^)]*\)/g 23 | const methodsRegExp = toRegExp(allMethods, 'g') 24 | 25 | export const transformPlugin = createUnplugin((options: PluginOptions) => { 26 | const { cache, layers, include, exclude, sourcemap, transformStyles, transformDirectives } = options 27 | 28 | return { 29 | name: `${libraryName}:transform`, 30 | enforce: 'post', 31 | transformInclude (id) { 32 | if (layers.some(layer => id.startsWith(layer))) { 33 | return true 34 | } 35 | if (exclude.some(pattern => id.match(pattern))) { 36 | return false 37 | } 38 | if (include.some(pattern => id.match(pattern))) { 39 | return true 40 | } 41 | }, 42 | async transform (code, id) { 43 | const styles = new Set() 44 | const directives: Required>[] = [] 45 | const s = new MagicString(code) 46 | let no = 0 47 | 48 | const addStyles = (style?: string) => { 49 | style && styles.add(style) 50 | } 51 | 52 | s.replace(componentsRegExp, (full, lazy, name) => { 53 | addStyles(transformStyles(camelize(name))) 54 | return full 55 | }) 56 | 57 | s.replace(methodsRegExp, (full, name) => { 58 | addStyles(transformStyles(camelize(name))) 59 | return full 60 | }) 61 | 62 | s.replace(directivesRegExp, (full, name) => { 63 | const directiveConfig = transformDirectives(camelize(name)) 64 | 65 | if (directiveConfig) { 66 | const [directive, path, style] = directiveConfig 67 | const aliasName = `__el_directive_${no}` 68 | 69 | no += 1 70 | addStyles(style) 71 | directives.push([directive, aliasName, path]) 72 | return aliasName 73 | } 74 | 75 | return full 76 | }) 77 | 78 | if (styles.size || directives.length) { 79 | let imports: string = '' 80 | 81 | for (const directive of directives) { 82 | imports += await genLibraryImport(directive, cache) 83 | } 84 | 85 | for (const style of styles) { 86 | imports += await genSideEffectsImport(style) 87 | } 88 | 89 | s.prepend(imports) 90 | } 91 | 92 | if (s.hasChanged()) { 93 | return { 94 | code: s.toString(), 95 | map: sourcemap 96 | ? s.generateMap({ source: id, includeContent: true }) 97 | : undefined 98 | } 99 | } 100 | } 101 | } 102 | }) 103 | -------------------------------------------------------------------------------- /src/module.ts: -------------------------------------------------------------------------------- 1 | import { addPluginTemplate, addTemplate, defineNuxtModule } from '@nuxt/kit' 2 | import { defaults, libraryName } from './config' 3 | import { 4 | resolveCache, 5 | resolveComponents, 6 | resolveDirectives, 7 | resolveGlobalConfig, 8 | resolveBaseImports, 9 | resolveImports, 10 | resolveInjection, 11 | resolveOptions, 12 | resolveStyles, 13 | resolveTeleports, 14 | resolveThemeChalk, 15 | resolveThemes, 16 | resolveMethods, 17 | transformPlugin, 18 | localePlugin 19 | } from './core/index' 20 | import { getLayersDir } from './utils' 21 | import type { ModuleOptions } from './types' 22 | export type { ModuleOptions } from './types' 23 | 24 | export default defineNuxtModule({ 25 | meta: { 26 | name: libraryName, 27 | configKey: 'elementPlus', 28 | compatibility: { 29 | nuxt: '>=3' 30 | } 31 | }, 32 | defaults, 33 | setup (options, nuxt) { 34 | const layers = getLayersDir(nuxt.options._layers) 35 | 36 | // disable the `cache` option when building applications 37 | if (!nuxt.options.dev) { 38 | options.cache = false 39 | } 40 | 41 | resolveOptions(options) 42 | resolveThemes(options) 43 | resolveBaseImports(options) 44 | nuxt.options.imports.autoImport !== false && resolveImports(options) 45 | nuxt.options.components !== false && resolveComponents(options) 46 | options.cache && addTemplate(resolveCache(options)) 47 | options.globalConfig && addPluginTemplate(resolveGlobalConfig(options)) 48 | options.importStyle === 'scss' && options.themeChalk && resolveThemeChalk(options).map(addTemplate) 49 | 50 | if (nuxt.options.ssr !== false) { 51 | addPluginTemplate(resolveInjection(options)) 52 | addPluginTemplate(resolveTeleports(options)) 53 | options.installMethods.length && addPluginTemplate(resolveMethods(options)) 54 | } 55 | 56 | nuxt.hook('vite:extendConfig', (config, { isClient }) => { 57 | const mode = isClient ? 'client' : 'server' 58 | 59 | config.plugins = config.plugins || [] 60 | config.plugins.push(transformPlugin.vite({ 61 | layers, 62 | cache: options.cache, 63 | include: options.include, 64 | exclude: options.exclude, 65 | sourcemap: nuxt.options.sourcemap[mode], 66 | transformStyles: name => resolveStyles(options, name), 67 | transformDirectives: name => resolveDirectives(options, name) 68 | })) 69 | 70 | if (options.defaultLocale !== 'en') { 71 | config.plugins.push(localePlugin.vite({ 72 | sourcemap: nuxt.options.sourcemap[mode], 73 | locale: options.defaultLocale 74 | })) 75 | } 76 | }) 77 | 78 | nuxt.hook('webpack:config', (configs) => { 79 | configs.forEach((config) => { 80 | const mode = config.name === 'client' ? 'client' : 'server' 81 | 82 | config.plugins = config.plugins || [] 83 | config.plugins.push(transformPlugin.webpack({ 84 | layers, 85 | cache: options.cache, 86 | include: options.include, 87 | exclude: options.exclude, 88 | sourcemap: nuxt.options.sourcemap[mode], 89 | transformStyles: name => resolveStyles(options, name), 90 | transformDirectives: name => resolveDirectives(options, name) 91 | })) 92 | 93 | if (options.defaultLocale !== 'en') { 94 | config.plugins.push(localePlugin.webpack({ 95 | sourcemap: nuxt.options.sourcemap[mode], 96 | locale: options.defaultLocale 97 | })) 98 | } 99 | }) 100 | }) 101 | } 102 | }) 103 | -------------------------------------------------------------------------------- /playground/components/other-card.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 75 | 76 | 138 | -------------------------------------------------------------------------------- /playground/components/navigation-card.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 118 | 119 | 127 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | import * as AllComponents from 'element-plus' 2 | import * as AllIcons from '@element-plus/icons-vue' 3 | import type { ElIdInjectionContext, ElZIndexInjectionContext } from 'element-plus' 4 | import type { Component } from 'vue' 5 | import { isVueComponent } from './utils' 6 | import type { ModuleOptions, PresetDirectives, PresetImport } from './types' 7 | 8 | export const libraryName = 'element-plus' 9 | 10 | export const iconLibraryName = '@element-plus/icons-vue' 11 | 12 | export const optimizeDeps = ['dayjs', 'dayjs/plugin/*.js', 'lodash-unified'] 13 | 14 | const allComponents = Object.entries(AllComponents).reduce((all, [key, item]) => { 15 | const regExp = /^El[A-Z]\w+/ 16 | if (isVueComponent(item) && regExp.test(key) && regExp.test((item as Component).name ?? '')) { 17 | all.push(key) 18 | } 19 | return all 20 | }, []) 21 | 22 | export const allIcons = Object.keys(AllIcons) 23 | 24 | export const allMethods: string[] = [ 25 | 'ElLoading', 26 | 'ElMessage', 27 | 'ElMessageBox', 28 | 'ElNotification' 29 | ] 30 | 31 | const allImports: PresetImport[] = [] 32 | 33 | const allBaseImports: PresetImport[] = [ 34 | ['ID_INJECTION_KEY', 'es/hooks/use-id/index.mjs'], 35 | ['ZINDEX_INJECTION_KEY', 'es/hooks/use-z-index/index.mjs'], 36 | ['provideGlobalConfig', 'es/components/config-provider/src/hooks/use-global-config.mjs'] 37 | ] 38 | 39 | const allNoStylesComponents: string[] = [ 40 | 'ElAutoResizer', 41 | 'ElCollection', 42 | 'ElCollectionItem', 43 | 'ElTooltipV2' 44 | ] 45 | 46 | const allDirectives: PresetDirectives = { 47 | Loading: ['ElLoadingDirective', 'ElLoading'], 48 | Popover: ['ElPopoverDirective', 'ElPopover'], 49 | InfiniteScroll: 'ElInfiniteScroll' 50 | } 51 | 52 | const allSubComponents: Record = { 53 | ElAnchor: ['ElAnchorLink'], 54 | ElBreadcrumb: ['ElBreadcrumbItem'], 55 | ElButton: ['ElButtonGroup'], 56 | ElCarousel: ['ElCarouselItem'], 57 | ElCheckbox: ['ElCheckboxButton', 'ElCheckboxGroup'], 58 | ElCollapse: ['ElCollapseItem'], 59 | ElCollection: ['ElCollectionItem'], 60 | ElContainer: ['ElAside', 'ElFooter', 'ElHeader', 'ElMain'], 61 | ElDescriptions: ['ElDescriptionsItem'], 62 | ElDropdown: ['ElDropdownItem', 'ElDropdownMenu'], 63 | ElForm: ['ElFormItem'], 64 | ElMenu: ['ElMenuItem', 'ElMenuItemGroup', 'ElSubMenu'], 65 | ElPopper: ['ElPopperArrow', 'ElPopperContent', 'ElPopperTrigger'], 66 | ElRadio: ['ElRadioGroup', 'ElRadioButton'], 67 | ElSkeleton: ['ElSkeletonItem'], 68 | ElSelect: ['ElOption', 'ElOptionGroup'], 69 | ElSplitter: ['ElSplitterPanel'], 70 | ElSteps: ['ElStep'], 71 | ElTable: ['ElTableColumn'], 72 | ElTableV2: ['ElAutoResizer'], 73 | ElTabs: ['ElTabPane'], 74 | ElTimeline: ['ElTimelineItem'], 75 | ElTour: ['ElTourStep'] 76 | } 77 | 78 | const defaultInjectionID: ElIdInjectionContext = { 79 | prefix: 1024, 80 | current: 0 81 | } 82 | 83 | const defaultInjectionZIndex: ElZIndexInjectionContext = { 84 | current: 0 85 | } 86 | 87 | const defaultInclude: RegExp[] = [ 88 | /\.vue$/, 89 | /\.vue\?vue/, 90 | /\.vue\?v=/, 91 | /\.((c|m)?j|t)sx?$/ 92 | ] 93 | 94 | const defaultExclude: RegExp[] = [ 95 | /[\\/]node_modules[\\/]/, 96 | /[\\/]\.git[\\/]/, 97 | /[\\/]\.nuxt[\\/]/ 98 | ] 99 | 100 | export const defaults: ModuleOptions = { 101 | components: allComponents, 102 | subComponents: allSubComponents, 103 | directives: allDirectives, 104 | imports: allImports, 105 | baseImports: allBaseImports, 106 | importStyle: 'css', 107 | themes: [], 108 | noStylesComponents: allNoStylesComponents, 109 | injectionID: defaultInjectionID, 110 | injectionZIndex: defaultInjectionZIndex, 111 | include: defaultInclude, 112 | exclude: defaultExclude, 113 | namespace: 'el', 114 | defaultLocale: 'en', 115 | appendTo: [], 116 | installMethods: [], 117 | icon: 'ElIcon' 118 | } 119 | -------------------------------------------------------------------------------- /playground/app.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 139 | 140 | 148 | -------------------------------------------------------------------------------- /playground/components/data-card.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 173 | 174 | 189 | -------------------------------------------------------------------------------- /playground/components/form-card.vue: -------------------------------------------------------------------------------- 1 | 111 | 112 | 216 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 |

5 | 6 | 7 | 8 | 9 | 10 | 11 |

12 | 13 | # Element Plus Nuxt 14 | 15 | > [Element Plus](https://element-plus.org) module for [Nuxt](https://nuxt.com) 16 | 17 | ## Features 18 | 19 | - Automatically import components and styles on demand. 20 | - Automatically import directives and styles on demand. 21 | - Automatically import icons from [@element-plus/icons-vue](https://github.com/element-plus/element-plus-icons). 22 | - Automatically import of ElMessage, ElNotification and other methods. 23 | - Automatically inject the ID_INJECTION_KEY and ZINDEX_INJECTION_KEY into Vue. 24 | - Automatically inject the teleport markup into the correct location in the final page HTML. 25 | 26 | ## Installation 27 | 28 | > [!WARNING] 29 | > Since the [dayjs](https://github.com/iamkun/dayjs) used internally by element-plus is not a [JavaScript modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules), in order to ensure that it can be converted into a JavaScript modules before startup, you need to add a `.npmrc` file to the root directory of the project and add the following configuration: 30 | > ``` 31 | > shamefully-hoist=true 32 | > node-linker=hoisted 33 | > ``` 34 | > Or install the `dayjs` dependency separately. 35 | 36 | ```bash 37 | npx nuxi@latest module add element-plus 38 | # or 39 | npm i element-plus @element-plus/nuxt -D 40 | ``` 41 | 42 | ## Configuration 43 | 44 | > [!WARNING] 45 | > At present, the method cannot automatically obtain the context, and you need to manually configure [installMethods](#installmethods) in the options. 46 | 47 | ```ts 48 | export default defineNuxtConfig({ 49 | modules: [ 50 | '@element-plus/nuxt' 51 | ], 52 | elementPlus: { /** Options */ } 53 | }) 54 | ``` 55 | 56 | ## Usage 57 | 58 | ```vue 59 | 64 | ``` 65 | 66 | Reference [Nuxt documentation](https://nuxt.com/docs/guide/directory-structure/components) and [playground](./playground/app.vue) use. 67 | 68 | ## Options 69 | 70 | ### importStyle 71 | 72 | - Type: `'css' | 'scss' | boolean` 73 | - Default: `css` 74 | 75 | import style css or sass(scss) with components, disable automatically import styles with `false`. 76 | 77 | ### themes 78 | 79 | - Type: `array` 80 | 81 | A list of themes that require import styles automatically. 82 | 83 | e.g. `['dark']` 84 | 85 | ### icon 86 | 87 | - Type: `string | false` 88 | - Default: `ElIcon` 89 | 90 | Icon prefix name, disable automatically import icon with `false`. 91 | 92 | ### installMethods 93 | 94 | - Type: `array` 95 | 96 | List of methods that need to be installed. 97 | 98 | e.g. `['ElLoading', 'ElMessage', 'ElMessageBox', 'ElNotification']` 99 | 100 | ### namespace 101 | 102 | - Type: `string` 103 | - Default: `el` 104 | 105 | When you change the global namespace, you must change it here as well. 106 | 107 | ### defaultLocale 108 | 109 | - Type: `string` 110 | 111 | Replace default locale, you can find locale list [here](https://github.com/element-plus/element-plus/tree/dev/packages/locale/lang) 112 | 113 | e.g. `'zh-cn'` 114 | 115 | ### cache 116 | 117 | - Type: `boolean` 118 | - Default: `false` 119 | 120 | Whether to cache the element-plus components and directives. **Only effective in development mode**. 121 | 122 | If you enable this feature, you will get faster loading speed in development mode. 123 | 124 | ### themeChalk 125 | 126 | - Type: `object` 127 | 128 | Configure SCSS variables for generating custom themes. **Only effective when `importStyle` is `scss`**. 129 | 130 | e.g. 131 | 132 | ```ts 133 | { 134 | $colors: { 135 | primary: { base: 'rgba(107,33,168, 1)' } 136 | }, 137 | dark: { 138 | $colors: { 139 | primary: { base: 'rgb(242, 216, 22)' } 140 | } 141 | } 142 | } 143 | ``` 144 | 145 | ### globalConfig 146 | 147 | - Type: `object` 148 | 149 | Set global configuration, such as modifying the default `size` and `z-index` of the component. 150 | 151 | e.g. `{ size: 'small', zIndex: 3000 }` 152 | 153 | ### injectionID 154 | 155 | - Type: `object` 156 | - Default: `{ prefix: 1024, current: 0 }` 157 | 158 | Automatically inject the ID_INJECTION_KEY into Vue. 159 | 160 | ### injectionZIndex 161 | 162 | - Type: `object` 163 | - Default: `{ current: 0 }` 164 | 165 | Automatically inject the Z_INDEX_INJECTION_KEY into Vue. 166 | 167 | ### appendTo 168 | 169 | - Type: `array` 170 | 171 | When you modify the `append-to` props in all based on ElTooltip components, you need to add the value here. 172 | 173 | ### components 174 | 175 | - Type: `array` 176 | 177 | If there are components that are not imported automatically from Element Plus, you need to add the component name here. 178 | 179 | e.g. `['ElSubMenu']` 180 | 181 | ### subComponents 182 | 183 | - Type: `object` 184 | 185 | A map of components that the definition file of subComponent is in its parent component. 186 | 187 | ### directives 188 | 189 | - Type: `object` 190 | 191 | If there are directives that are not imported automatically from Element Plus, you need to add the directive name here. 192 | 193 | ### imports 194 | 195 | - Type: `array` 196 | 197 | If you wish to add automatically import content from Element Plus, you can add it here. 198 | 199 | e.g. 200 | 201 | ```ts 202 | [ 203 | ['useLocale', 'es/hooks/use-locale/index.mjs'], 204 | [['castArray', 'unique'], 'es/utils/arrays.mjs'] 205 | ], 206 | ``` 207 | 208 | ### baseImports 209 | 210 | - Type: `array` 211 | 212 | List of imports that will be imported whether if autoImports is disabled. 213 | 214 | ### noStylesComponents 215 | 216 | - Type: `array` 217 | 218 | When a component incorrectly loads styles, you need to add the component name here. 219 | 220 | ### include 221 | 222 | - Type: `array` 223 | - Default: `[ /\.vue$/, /\.vue\?vue/, /\.vue\?v=/, /\.((c|m)?j|t)sx?$/]` 224 | 225 | Include files that need to automatically import styles. 226 | 227 | ### exclude 228 | 229 | - Type: `array` 230 | - Default: `[/[\\/]node_modules[\\/]/, /[\\/]\.git[\\/]/, /[\\/]\.nuxt[\\/]/]` 231 | 232 | Exclude files that do not require the automatic import of styles. 233 | 234 | ## Template 235 | 236 | [element-plus-nuxt-starter](https://github.com/element-plus/element-plus-nuxt-starter) 237 | 238 | ## Development 239 | 240 | - Run `pnpm i` to install the dependencies. 241 | - Run `pnpm dev:prepare` to generate type stubs. 242 | - Run `pnpm dev` to start playground in development mode. 243 | - Run `pnpm dev:build` to build playground. 244 | - Run `pnpm dev:start` to locally preview playground. 245 | - Run `pnpm build` to build this project. 246 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import type { ElIdInjectionContext, ElZIndexInjectionContext, ConfigProviderContext } from 'element-plus' 2 | 3 | export type Themes = 'dark' 4 | 5 | /** name: export name from the library, as: the name you want to use in your project, from: the name of library */ 6 | export type PresetComponent = string | [name: string, as?: string, from?: string] 7 | 8 | /** name: export name from the library, path: the file path in the component directory */ 9 | export type PresetImport = [name: string | string[], path: string] 10 | 11 | /** directive: export name from the library, name: export name with style */ 12 | export type PresetDirectives = Record 13 | 14 | /** Used to filter files that need to automatically import styles and other functions */ 15 | export interface TransformOptions { 16 | include: RegExp[] 17 | exclude: RegExp[] 18 | } 19 | 20 | export type ScssVariables = { [k in Key]?: Value } & { [k: string]: Value } 21 | 22 | export interface ScssChalk { 23 | '$colors'?: Partial> & ScssVariables<'white' | 'black', string | { base?: string }> 24 | '$text-color'?: ScssVariables<'primary' | 'regular' | 'secondary' | 'placeholder' | 'disabled'> 25 | '$bg-color'?: ScssVariables<'' | 'page' | 'overlay'> 26 | '$border-color'?: ScssVariables<'' | 'light' | 'lighter' | 'extra-light' | 'dark' | 'darker'> 27 | '$fill-color'?: ScssVariables<'' | 'light' | 'lighter' | 'extra-light' | 'dark' | 'darker' | 'blank'> 28 | '$border-radius'?: ScssVariables<'base' | 'small' | 'round' | 'circle'> 29 | '$box-shadow'?: ScssVariables<'' | 'light' | 'lighter' | 'dark', string | string[]> 30 | '$font-family'?: ScssVariables<''> 31 | '$font-size'?: ScssVariables<'extra-large' | 'large' | 'medium' | 'base' | 'small' | 'extra-small'> 32 | '$z-index'?: ScssVariables<'normal' | 'top' | 'popper'> 33 | '$common-component-size'?: ScssVariables<'large' | 'default' | 'small'> 34 | '$overlay-color'?: ScssVariables<'' | 'light' | 'lighter'> 35 | '$mask-color'?: ScssVariables<'' | 'extra-light'> 36 | [key: `$${string}`]: string | undefined | ScssVariables> 37 | } 38 | 39 | export type ThemeChalk = ScssChalk & Partial> 40 | 41 | export interface ModuleOptions extends TransformOptions { 42 | /** 43 | * A list of components that need to be automatically imported externally. 44 | * 45 | * @default 'from element-plus/es/component' 46 | * 47 | * If there are components that are not imported automatically from Element Plus, you need to add the component name here. 48 | * 49 | * @example 50 | * ```ts 51 | * ['ElSubMenu'] 52 | * ``` 53 | */ 54 | components: PresetComponent[] 55 | /** 56 | * A map of components that the definition file of subComponent is in its parent component. 57 | */ 58 | subComponents: Record 59 | /** 60 | * A list of directives that need to be automatically imported externally. 61 | * 62 | * @default 63 | * ```ts 64 | * { 65 | * Loading: ['ElLoadingDirective', 'ElLoading'], 66 | * Popover: ['ElPopoverDirective', 'ElPopover'], 67 | * InfiniteScroll: 'ElInfiniteScroll' 68 | * } 69 | * ``` 70 | */ 71 | directives: PresetDirectives 72 | /** 73 | * A list of imports that need to be automatically imported externally. 74 | * 75 | * When you need to add automatically import content from Element Plus, you can add it here. 76 | * 77 | * @example 78 | * ```ts 79 | * [ 80 | * ['useLocale', 'es/hooks/use-locale/index.mjs'], 81 | * [['castArray', 'unique'], 'es/utils/arrays.mjs'] 82 | * ] 83 | * ``` 84 | * 85 | * @before 86 | * ```ts 87 | * import { useLocale } from 'element-plus' 88 | * const { t } = useLocale() 89 | * ``` 90 | * 91 | * @after 92 | * ```ts 93 | * const { t } = useLocale() 94 | * ``` 95 | */ 96 | imports: PresetImport[] 97 | /** 98 | * 99 | * List of imports that will be imported whether if autoImports is disabled. 100 | * 101 | * @default 102 | * ```ts 103 | * [ 104 | * ["ID_INJECTION_KEY", "es/hooks/use-id/index.mjs"], 105 | * ["ZINDEX_INJECTION_KEY", "es/hooks/use-z-index/index.mjs"], 106 | * ["provideGlobalConfig", "es/components/config-provider/src/hooks/use-global-config.mjs"], 107 | * ] 108 | * ``` 109 | */ 110 | baseImports: PresetImport[] 111 | /** 112 | * import style css or scss with components 113 | * 114 | * @default 'css' 115 | * 116 | * Disable automatically import styles with `false` 117 | */ 118 | importStyle: 'css' | 'scss' | boolean 119 | /** 120 | * A list of themes that require importing styles automatically. 121 | * 122 | * Currently, only [dark](https://element-plus.org/en-US/guide/dark-mode.html) is supported. 123 | * 124 | * @example 125 | * ```ts 126 | * ['dark'] 127 | * ``` 128 | */ 129 | themes: Themes[] 130 | /** 131 | * A list of component names that have no styles, so resolving their styles file should be prevented 132 | * 133 | * @default 134 | * ```ts 135 | * ['ElAutoResizer', 'ElTooltipV2'] 136 | * ``` 137 | */ 138 | noStylesComponents: string[] 139 | /** 140 | * We need to inject the same ID value into the server side and client side to ensure that the code generated on the server is the same as that on the client, so as to avoid hydrate errors. 141 | * 142 | * @default 143 | * ```ts 144 | * { prefix: 1024, current: 0 } 145 | * ``` 146 | */ 147 | injectionID: ElIdInjectionContext 148 | /** 149 | * We need to inject an initial z-index value to ensure that the server side and client side generate the same z-index value, so as to avoid hydration errors. 150 | * 151 | * @default 152 | * ```ts 153 | * { current: 0 } 154 | * ``` 155 | */ 156 | injectionZIndex: ElZIndexInjectionContext 157 | /** 158 | * Global component className prefix. 159 | * 160 | * When you change the global namespace, you must change it here as well. 161 | * 162 | * @default 'el' 163 | */ 164 | namespace: string 165 | /** 166 | * Which element the tooltip CONTENT appends to. 167 | * 168 | * When you modify the `append-to` props in all based on ElTooltip components, you need to add the value here, to avoid hydrate errors. 169 | * 170 | * If you used `Teleport` to teleport a part of a component's template into a DOM node near the `` tag, you can also add this ID here. The internal plug-in will automatically handle hydrate errors. 171 | */ 172 | appendTo: string[] 173 | /** 174 | * Icon prefix name. 175 | * 176 | * To avoid the duplication of icon names with native DOM or other components, we recommend adding a prefix to the icon. 177 | * 178 | * @default 'ElIcon' 179 | * 180 | * Disable automatically import icon with `false` 181 | */ 182 | icon: false | string 183 | /** 184 | * Replace default locale, you can find locale list [here](https://github.com/element-plus/element-plus/tree/dev/packages/locale/lang). 185 | * 186 | * @default 'en' 187 | * @example 'zh-cn' 188 | */ 189 | defaultLocale: string 190 | /** 191 | * Set global configuration, such as modifying the default size and z-index of the component. 192 | * 193 | * @example 194 | * ```ts 195 | * { size: 'small', zIndex: 3000 } 196 | * ``` 197 | */ 198 | globalConfig?: ConfigProviderContext 199 | /** 200 | * List of methods that need to be installed. 201 | * 202 | * @example 203 | * ```ts 204 | * ['ElLoading', 'ElMessage', 'ElMessageBox', 'ElNotification'] 205 | * ``` 206 | */ 207 | installMethods: Array<'ElLoading' | 'ElMessage' | 'ElMessageBox' | 'ElNotification'> 208 | /** 209 | * Whether to cache the element-plus components and directives. **Only effective in development mode**. 210 | * 211 | * If you enable this feature, you will get faster loading speed in development mode. 212 | * 213 | * @default 'false' 214 | */ 215 | cache?: boolean 216 | /** 217 | * Configure SCSS variables for generating custom themes. **Only effective when `importStyle` is `scss`**. 218 | */ 219 | themeChalk?: ThemeChalk 220 | } 221 | --------------------------------------------------------------------------------