webpack(二)

274 阅读5分钟

接下来我们来解析 CSS SCSS LESS ES6 图片 ,为什么我们要解析这些东西,因为webpack只认js文件。
先在 src 下新建一个 index.css,并且在 index.js 中引入(import './index.css';) 解析CSS 需要两个 loader,分别是 css-loader style-loader
css-loader的作用是解析CSS语法,并且把解析后的结果传递给 style-loader
style-loader的作用是将传递的css变成style标签插入到html中,所以我们要先用 css-loader,再用 style-loader。

loader 的执行顺序

默认是从下到上执行,从右到左执行。

npm install css-loader style-loader --save-dev

我们在 webpack.base.js 中的 base 对象中新增一个对象,并在index.css中写入 background:red;

const base = {
    ...
    module:{
        // 转化什么文件 用什么去转 使用哪些loader
        // loader 的写法有三种 [] {} ''
        rules:[
            {
                test:/\.css$/, // 转换以css结尾的文件
                use:['style-loader','css-loader']
            }
        ]
    },
    ...
}

再 npm run dev 执行下试试

这就算是转换了CSS。
我们再看 scss less 怎么转换。
sass 需要的包 node-sass sass-loader
less 需要的包 less less-loader
stylus 需要的包 stylus stylus-loader
我们先来看看 sass,先在src 下新建个 a.scss文件,并写入

$background:black;
div{
    width:100px;
    height:100px;
    background:$background;
}

并在 index.js 中引入(import './a.scss'),接着安装sass需要的包,并配置loader,同样是在webpack.base.js中

...
rules:[
    {
        test:/\.css$/, // 转换以css结尾的文件
        use:['style-loader','css-loader']
    },
    {
        // 匹配到scss结尾的文件,使用sass-loader来调用node-sass处理sass文件
        test:/\.scss$/, // 转换以scss结尾的文件
        use:['style-loader','css-loader','sass-loader']
    }
]
...

接着 npm run dev 运行下

还有一种比较恶心的情况,就是css中引入了scss它是不会再去解析的,需要我们自己去配,自己试吧,反正我是不会这样写。

...
rules:[
    {
        test:/\.css$/, // 转换以css结尾的文件
        use:['style-loader',{
            loader:'css-loader',
            options:{
                 // 给loader传递参数
                 // 表示如果从 css中又引入了sass文件,需要先从此loader之后执行一下后面的这个loader
                // 此处写几,就表示要先走后面的几个loader
                importLoaders:1
           }
        },'sass-loader']
    }
]
...

打包CSS时可能还会遇到这样一种问题,如果你写了一些CSS3的语法,还需要兼容一些低版本的浏览器,我们还需要自己加前缀,此时就需要用到postcss-loader

npm install postcss-loader --save-dev

这个loader需要一个插件去支持 autoprefixer

npm install autoprefixer --save-dev

先说下它的配置,在css加上前缀后再转给style-loader,它需要在最后一个loader前面。

...
rules:[
    {
        test:/\.css$/, // 转换以css结尾的文件
        use:['style-loader','postcss-loader','css-loader']
    },
    {
        // 匹配到scss结尾的文件,使用sass-loader来调用node-sass处理sass文件
        test:/\.scss$/, // 转换以scss结尾的文件
        use:['style-loader','css-loader','postcss-loader','sass-loader']
    }
]
...

此处我在a.scss文件中给 div 加了一个 transform: rotate(45deg)
声明一下,postcss需要有它自己的一个配置文件,叫postcss.config.js,所以我们新建一下它。再新建一个.browserslistrc文件,告诉postcss,我要覆盖95%的浏览器。

再运行下

再看打包出来的文件,已经把前缀加上了

这么写样式,开发时是OK的,但上线时就恶心了,因为解析CSS的时候就不能渲染html了,因为它是单线程的。所以我们希望css和js能并行,那就可以单独把css抽离出来了,那我们又要用到另一个插件了 mini-css-extract-plugin。

npm install mini-css-extract-plugin --save-dev
// 先在 webapck.base.js 中引入
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
// 开发环境下尽量别抽离,因为慢,此处我们判断下环境
...
rules:[
    {
        test:/\.css$/, // 转换以css结尾的文件
        use:[isDev ? 'style-loader' : MiniCssExtractPlugin ,'css-loader']
    },
    ...
]
plugins:[
    // 不是开发环境才用
    !isDev && new MiniCssExtractPlugin({
        // 抽离出来的CSS 文件名
        filename:'css/main.css'
    }),
    ...
].filter(Boolean)
// 此处传Boolean 是因为 MiniCssExtractPlugin 的判断,如果是开发环境会返回个false
// false 在数组里不是找死嘛,此处我们用这个技巧,Boolean 是个函数,它可以帮我们把每个值变成一个布尔类型
// 如果是 false 的话 filter 会过滤掉
...

正确来讲,是把CSS抽离出来的,但是它没压缩,但我希望生产环境下CSS可以压缩,压缩CSS需要用到一个插件

npm install optimize-css-assets-webpack-plugin --save-dev

它有一个缺点,就是如果使用这个插件压缩了CSS,那JS它就不管了,你还要自己压缩

npm install terser-webpack-plugin --save-dev

此处我们在 webpack.prod.js 中进行配置

// 压缩CSS
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
// 压缩JS
const TerserWebpackPlugin = require('terser-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
    mode:'production',
    optimization:{
        // 优化项
        minimizer:[
            // 可以放压缩方案
            new OptimizeCSSAssetsPlugin(),
            new TerserWebpackPlugin()
        ]
    },
    plugins:[
        // 从 base 中挪过来的,只在生产环境下才清空打包目录比较好
        new CleanWebpackPlugin(),
    ]
}


自己运行 build 看看吧。接下来我们处理图片,先在src目录下放一张图片,并在index.js中引入

// 引入图片
import logo from './logo.png';
// 需求,获取当前打包logo图片后的路径,并把路径给到src
let img = document.createElement('img');
img.src = logo;
document.body.appendChild(img);

解析图片需要对应的loader,此处用到的是 file-loader

npm install file-loader --save-dev 

安装完后该配置了,在 base 配置中增加

...
module: {
    rules: [
        {
            test:/\.(jpg|png|gif|jpeg)$/,
            use:'file-loader'
        }
    ]
}
...

看 图片有了

file-loader 默认功能是拷贝的作用,但我希望比较小的图片可以转为base64,好处是不用发送,就需要用到另一个loader了

npm install url-loader --save-dev

并将上面的loader替换掉

module: {
    rules: [
        {
            test:/\.(jpg|png|gif|jpeg)$/,
            use:{
                loader:'url-loader', 
                // 优化 如果大于100k的图片会默认使用file-loader
                options:{
                    limit:100*1024,
                    name:'image/[contentHash].[ext]' // 打包放到image目录下
                }
            }
        }
        ]
    }

我们再配置一下图标

...
{
    test:/\.(woff|ttf|eot|svg)$/,
    use:'file-loader' // 对图标的处理,直接拷贝过去就行了
}
...

开始配置JS的转换,多是用于将es6转为es5,用的是babel的插件
babel-loader 是 babel 和 webpack 的桥梁,它默认会调 @babel/core,@babel/core会调用 @babel/preset-env 将 es6 转为 es5
为什么要加@,这个东西叫作用域,就是说把所有叫babel的包都放到babel的作用域下,省的重名

npm install @babel/core @babel/preset-env babel-loader --save-dev

在 index.js 中写一个 es6 语法

// 实现将es6 转为 es5
const fn = () => { }
fn()

在 webpack.base.js 中配置

rules: [
    {
        test:/\.js$/,
        use:'babel-loader'
    },
]

并且新建一个babel的配置文件(.babelrc),默认转换时会掉这个文件

{
    "presets":[
        // 执行顺序是从下到上  配的是多个插件 
        "@babel/preset-env"
    ],
    "plugins": [
        // 执行顺序是从上到下  配的是单个插件
    ]
}

再运行下,看下打包出来的有没有转换

再来写个类试试

看,报错了,它说它不识别,因为类是草案语法,需要单独安装这个插件,它说装啥就装啥。

npm install @babel/plugin-proposal-class-properties --save-dev

再改一下babel的配置文件

{
    "presets":[
        // 执行顺序是从下到上  配的是多个插件(插件包) 
        "@babel/preset-env"
    ],
    "plugins": [
        // 执行顺序是从上到下  配的是单个插件
        // "babel/plugin-proposal-class-properties" 
        // 如果要传参的话,需要把它写过一个数组中
        ["@babel/plugin-proposal-class-properties",{"loose":true}]
    ]
}

再打包试试

很好,解析出来了
如果你要写装饰器的话,也需要单独安装插件,装完在babel配置中再配一下,自己装吧,反正我不用。

{
    "presets":[
        // 执行顺序是从下到上  配的是多个插件(插件包) 
        "@babel/preset-env"
    ],
    "plugins": [
        // 执行顺序是从上到下  配的是单个插件
        // "babel/plugin-proposal-class-properties" 
        // 如果要传参的话,需要把它写过一个数组中
         ["@babel/plugin-proposal-decorators",{"legacy":true}],
        ["@babel/plugin-proposal-class-properties",{"loose":true}]
    ]
}

[1,2,3].includes(2)。像这种实例上的语法它也不会转换,包括promise它也不会,就是不会转换API。 改一下 .bablerc

{
    "presets":[
        // 使用的API会自动转换,并且会按需加载
        ["@babel/preset-env",{
            "useBuiltIns":"usage",
            "corejs":2 // npm install core-js@2 --save-dev   也可以用3
        }]
    ],
    "plugins": [
        // 解析装饰器
        ["@babel/plugin-proposal-decorators",{"legacy":true}],
        // 解析类
        ["@babel/plugin-proposal-class-properties",{"loose":true}]
    ]
}

如果你的index.js中有一个A类,同时又引入了另一个JS,那个JS中同样也有一个A类,怎么办?需要用到 @babel/plugin-transform-runtime 这个插件,这个插件依赖于 @babel/runtime 插件,但它是自动帮我们调的,所以我们只需要安装 @babel/plugin-transform-runtime 就行了。

npm install @babel/plugin-transform-runtime --save-dev

再配置一下

{
    "presets":[
        // 使用的API会自动转换,并且会按需加载
        ["@babel/preset-env",{
            "useBuiltIns":"usage",
            "corejs":2 // npm install core-js@2 --save-dev   也可以用3
        }]
    ],
    "plugins": [
        // 解析装饰器
        ["@babel/plugin-proposal-decorators",{"legacy":true}],
        // 解析类
        ["@babel/plugin-proposal-class-properties",{"loose":true}],
        "@babel/plugin-transform-runtime"
    ]
}

基本用法就到这里吧,下一章写怎么和VUE接合到一起,因为我们项目就是用的VUE。