UNPKG

san-cli-service

Version:

定制化的前端工程构建工具

247 lines (227 loc) 9.86 kB
/** * Copyright (c) Baidu Inc. All rights reserved. * * This source code is licensed under the MIT license. * See LICENSE file in the project root for license information. * * @file css webpack * inspired by https://github.com/vuejs/vue-cli/blob/dev/packages/%40vue/cli-service/lib/config/css.js */ const semver = require('semver'); const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); const {cssnanoOptions: defaultCssnanoOptions} = require('../options'); const {findExisting} = require('san-cli-utils/path'); const {warn} = require('san-cli-utils/ttyLogger'); module.exports = { id: 'built-in:css', apply(api, rootOptions) { api.chainWebpack(webpackConfig => { const {getAssetPath} = require('san-cli-utils/path'); const isProd = api.isProd(); const cssOptions = rootOptions.css || {}; const rootSourceMap = !!rootOptions.sourceMap; // 这里loaderOptions直接用 projectOptions.css 的内容 // prettier-ignore const { extract = isProd, // 不在 css 中单独配置,默认跟 rootOptions.sourceMap 一致 sourceMap = rootSourceMap, loaderOptions = {}, cssPreprocessor, cssnanoOptions } = cssOptions; const postCSSOptions = loaderOptions.postcss; // prettier-ignore const hasPostCSSConfig = !!( postCSSOptions || api.service.pkg.postcss || findExisting( ['.postcssrc', '.postcssrc.js', 'postcss.config.js', '.postcssrc.yaml', '.postcssrc.json'], api.resolve('.') ) ); let {requireModuleExtension} = rootOptions.css || {}; if (typeof requireModuleExtension === 'undefined') { if (loaderOptions.css && loaderOptions.css.modules) { throw new Error( '`css.requireModuleExtension` is required when custom css modules options provided' ); } requireModuleExtension = true; } const shouldExtract = extract !== false; const filename = getAssetPath( rootOptions.assetsDir, `css/[name]${rootOptions.filenameHashing ? '.[contenthash:8]' : ''}.css` ); const extractOptions = Object.assign( { filename, chunkFilename: filename }, extract && typeof extract === 'object' ? extract : {} ); // use relative publicPath in extracted CSS based on extract location // use config publicPath first const cssPublicPath = rootOptions.publicPath || '../'.repeat( extractOptions.filename.replace(/^\.[\/\\]/, '').split(/[\/\\]/g).length - 1 ); // 优先使用 san.config 定义的内容 const styleLoader = require('./loaders/style')(loaderOptions.style, rootOptions, api); function createCSSRule(lang, test, loader, options) { const baseRule = webpackConfig.module.rule(lang).test(test); // rules for *.module.* files const extModulesRule = baseRule.oneOf('normal-modules').test(/\.module\.\w+$/); applyLoaders(extModulesRule, true); // rules for <style module> files const sanModulesRule = baseRule.oneOf('san-modules').resourceQuery(/module/); applyLoaders(sanModulesRule, true); // rules for normal CSS imports const normalRule = baseRule.oneOf('normal'); applyLoaders(normalRule, !requireModuleExtension); function applyLoaders(rule, isCssModule) { if (shouldExtract) { rule.use('extract-css-loader') .loader(require('mini-css-extract-plugin').loader) .options({ hmr: !isProd, publicPath: cssPublicPath }); } else { rule.use(styleLoader.name) .loader(styleLoader.loader) .options(styleLoader.options); } const cssLoaderOptions = Object.assign( { sourceMap, importLoaders: // prettier-ignore 1 + (hasPostCSSConfig ? 1 : 0) }, loaderOptions.css ); if (isCssModule) { cssLoaderOptions.modules = { localIdentName: '[name]_[local]_[hash:base64:5]', ...cssLoaderOptions.modules }; } else { delete cssLoaderOptions.modules; } rule.use('css-loader') .loader('css-loader') .options(cssLoaderOptions); if (hasPostCSSConfig) { rule.use('postcss-loader') .loader('postcss-loader') .options( Object.assign( { sourceMap, config: { // 从项目根目录查找 postcss config path: api.getCwd() } }, postCSSOptions ) ); } if (loader) { rule.use(loader) .loader(loader) .options(Object.assign({sourceMap}, options)); } } } createCSSRule('css', /\.css$/); createCSSRule('postcss', /\.p(ost)?css$/); if (!cssPreprocessor || cssPreprocessor === 'sass') { let sassLoaderVersion; try { sassLoaderVersion = semver.major(require('sass-loader/package.json').version); } catch (e) {} if (sassLoaderVersion < 8) { warn('A new version of sass-loader is available. Please upgrade for best experience.'); } // 添加 sass 逻辑 createCSSRule('scss', /\.scss$/, 'sass-loader', Object.assign({}, loaderOptions.sass)); if (sassLoaderVersion < 8) { createCSSRule( 'sass', /\.sass$/, 'sass-loader', Object.assign( {}, { indentedSyntax: true }, loaderOptions.sass ) ); } else { createCSSRule( 'sass', /\.sass$/, 'sass-loader', Object.assign(loaderOptions.sass || {}, { sassOptions: Object.assign({}, loaderOptions.sass && loaderOptions.sass.sassOptions, { indentedSyntax: true }) }) ); } } if (!cssPreprocessor || cssPreprocessor === 'less') { createCSSRule( 'less', /\.less$/, 'less-loader', Object.assign( { javascriptEnabled: true, compress: false }, loaderOptions.less ) ); } if (!cssPreprocessor || cssPreprocessor === 'stylus') { createCSSRule( 'stylus', /\.styl(us)?$/, 'stylus-loader', Object.assign( { preferPathResolver: 'webpack' }, loaderOptions.stylus ) ); } // inject CSS extraction plugin if (shouldExtract) { webpackConfig.plugin('extract-css').use(require('mini-css-extract-plugin'), [extractOptions]); // minify extracted CSS if (isProd) { const nanoOptions = { preset: ['default', Object.assign(defaultCssnanoOptions, cssnanoOptions)] }; // 压缩 webpackConfig.optimization.minimizer('css').use( new OptimizeCSSAssetsPlugin({ assetNameRegExp: /\.css$/g, cssProcessorOptions: nanoOptions, canPrint: true }) ); } } }); } };