1 | const logger = require('@poi/logger')
|
2 |
|
3 | exports.name = 'builtin:config-css'
|
4 |
|
5 | exports.apply = api => {
|
6 | api.hook('createCLI', ({ command }) => {
|
7 | if (api.isProd) {
|
8 | command.option('--no-extract-css', `Don't extract CSS files`)
|
9 | } else {
|
10 | command.option('--extract-css', 'Extract CSS files')
|
11 | }
|
12 | })
|
13 |
|
14 | api.hook('createWebpackChain', (config, { type }) => {
|
15 | const { loaderOptions = {}, extract: shouldExtract } = api.config.css || {}
|
16 | const sourceMap = Boolean(api.config.output.sourceMap)
|
17 | const isServer = type === 'server'
|
18 |
|
19 | const hasPostCSSConfig = api.configLoader.resolve({
|
20 | files: [
|
21 | 'postcss.config.js',
|
22 | 'package.json',
|
23 | '.postcssrc',
|
24 | '.postcssrc.js',
|
25 | '.postcssrc.yaml',
|
26 | '.postcssrc.json'
|
27 | ],
|
28 | packageKey: 'postcss'
|
29 | })
|
30 |
|
31 | if (hasPostCSSConfig) {
|
32 | logger.debug('Applying custom PostCSS config at ' + hasPostCSSConfig)
|
33 | }
|
34 |
|
35 |
|
36 |
|
37 |
|
38 | const needInlineMinification = api.config.output.minimize && !shouldExtract
|
39 |
|
40 | const cssnanoOptions = {
|
41 | safe: true,
|
42 | autoprefixer: { disable: true },
|
43 | mergeLonghand: false
|
44 | }
|
45 | if (sourceMap) {
|
46 | cssnanoOptions.map = { inline: false }
|
47 | }
|
48 |
|
49 | const extractOptions = {
|
50 | filename: api.config.output.fileNames.css,
|
51 | chunkFilename: api.config.output.fileNames.css.replace(
|
52 | /\.css$/,
|
53 | '.chunk.css'
|
54 | )
|
55 | }
|
56 |
|
57 | const createCSSRule = (lang, test, loader, options) => {
|
58 | const applyLoaders = (rule, modules) => {
|
59 | if (!isServer) {
|
60 | if (shouldExtract) {
|
61 | rule
|
62 | .use('extract-css-loader')
|
63 | .loader(require('mini-css-extract-plugin').loader)
|
64 | } else {
|
65 | rule
|
66 | .use('vue-style-loader')
|
67 | .loader(require.resolve('vue-style-loader'))
|
68 | .options({
|
69 | sourceMap
|
70 | })
|
71 | }
|
72 | }
|
73 |
|
74 | const cssLoaderOptions = Object.assign(
|
75 | {
|
76 | sourceMap,
|
77 | modules,
|
78 | localIdentName:
|
79 | api.mode === 'production'
|
80 | ? '[local]_[hash:base64:5]'
|
81 | : '[path][name]__[local]--[hash:base64:5]',
|
82 | importLoaders:
|
83 | 1 +
|
84 | (hasPostCSSConfig ? 1 : 0) +
|
85 | (needInlineMinification ? 1 : 0)
|
86 | },
|
87 | loaderOptions.css
|
88 | )
|
89 |
|
90 | rule
|
91 | .use('css-loader')
|
92 | .loader(isServer ? 'css-loader/locals' : 'css-loader')
|
93 | .options(cssLoaderOptions)
|
94 |
|
95 | if (needInlineMinification) {
|
96 | rule
|
97 | .use('minify-inline-css')
|
98 | .loader('postcss-loader')
|
99 | .options({
|
100 | plugins: [require('cssnano')(cssnanoOptions)]
|
101 | })
|
102 | }
|
103 |
|
104 | if (hasPostCSSConfig) {
|
105 | rule
|
106 | .use('postcss-loader')
|
107 | .loader('postcss-loader')
|
108 | .options(Object.assign({ sourceMap }, loaderOptions.postcss))
|
109 | }
|
110 |
|
111 | if (loader) {
|
112 | rule
|
113 | .use(loader)
|
114 | .loader(loader)
|
115 | .options(Object.assign({ sourceMap }, options))
|
116 | }
|
117 | }
|
118 |
|
119 | const baseRule = config.module.rule(lang).test(test)
|
120 |
|
121 |
|
122 | const vueModulesRule = baseRule
|
123 | .oneOf('vue-modules')
|
124 | .resourceQuery(/module/)
|
125 | applyLoaders(vueModulesRule, true)
|
126 |
|
127 |
|
128 | const vueNormalRule = baseRule.oneOf('vue').resourceQuery(/\?vue/)
|
129 | applyLoaders(vueNormalRule, false)
|
130 |
|
131 |
|
132 | const extModulesRule = baseRule
|
133 | .oneOf('normal-modules')
|
134 | .test(/\.module\.\w+$/)
|
135 | applyLoaders(extModulesRule, true)
|
136 |
|
137 |
|
138 | const normalRule = baseRule.oneOf('normal')
|
139 | applyLoaders(normalRule, false)
|
140 | }
|
141 |
|
142 | if (shouldExtract) {
|
143 | config
|
144 | .plugin('extract-css')
|
145 | .use(require('mini-css-extract-plugin'), [extractOptions])
|
146 |
|
147 | const OptimizeCSSPlugin = require('@intervolga/optimize-cssnano-plugin')
|
148 | config.plugin('optimize-css').use(OptimizeCSSPlugin, [
|
149 | {
|
150 | sourceMap,
|
151 | cssnanoOptions
|
152 | }
|
153 | ])
|
154 | }
|
155 |
|
156 | createCSSRule('css', /\.css$/)
|
157 | createCSSRule('postcss', /\.p(ost)?css$/)
|
158 |
|
159 | const sassImplementation = api.hasDependency('sass')
|
160 | ? api.localRequire('sass')
|
161 | : undefined
|
162 | createCSSRule(
|
163 | 'scss',
|
164 | /\.scss$/,
|
165 | 'sass-loader',
|
166 | Object.assign(
|
167 | {
|
168 | implementation: sassImplementation
|
169 | },
|
170 | loaderOptions.sass
|
171 | )
|
172 | )
|
173 | createCSSRule(
|
174 | 'sass',
|
175 | /\.sass$/,
|
176 | 'sass-loader',
|
177 | Object.assign(
|
178 | {
|
179 | indentedSyntax: true,
|
180 | implementation: sassImplementation
|
181 | },
|
182 | loaderOptions.sass
|
183 | )
|
184 | )
|
185 |
|
186 | createCSSRule('less', /\.less$/, 'less-loader', loaderOptions.less)
|
187 | createCSSRule(
|
188 | 'stylus',
|
189 | /\.styl(us)?$/,
|
190 | 'stylus-loader',
|
191 | Object.assign(
|
192 | {
|
193 | preferPathResolver: 'webpack'
|
194 | },
|
195 | loaderOptions.stylus
|
196 | )
|
197 | )
|
198 | })
|
199 | }
|