webpack(1)之基本配置

简介

  webpack是一个模块打包机,它会从指定入口文件开始,递归的寻找JavaScript模块以及其余一些浏览器没法直接运行的扩展语言(Sass, TypeScript)等,将其打包成合适的格式以供浏览器使用。
  它的做用有代码转换(利用各类loader将浏览器没法识别的语言转换成合适的格式),文件优化(好比说打包时压缩体积),代码分割,模块合并,自动刷新(热更新),代码校验,自动发布。 引用了网上的一张图来大体看一下webpack的运行机制: css

webpack的一些基本配置

安装webpack以及默认配置

  首先是安装webpack,在本地项目文件夹下npm init初始化以后,下载webpack以及webpack-cli:html

npm init -y
npm i webpack webpack-cli -D
复制代码

  此时在文件夹下创建一个src文件夹,用于放置项目代码。webpack此时能够进行0配置打包,在命令行输入npx webpack能够打包出一个dist文件夹,下面有一个main.js就是打包后的文件。这个打包后的文件内容,就是使用递归的方式解析src中的js模块,递归的方法名为__webpack_require__,它支持咱们在浏览器中使用CommonJs规范。
  默认打包的配置很弱,它只能识别js模块,在没有配置的状况下,webpack就至关于一个js模块打包机。固然咱们不可能直接就0配置打包一个项目,下面我总结一下webpack中经常使用的一些基本配置。前端

webpack.config.js

  webpack中默认的配置文件名为webpack.config.js,在根目录下创建一个名为webpack.config.js的文件,就能够在这个文件中写配置项。它的内容遵循CommonJs规范,webpack提供给咱们修改这个文件名的一些方法:
  (1)打包时输入命令npx webpack --config webpack.config.my.js
  (2)为了避免用每次都在命令行输出一串这么长的命令,在package.json中配置scripts,"build" : "webpack --config webpack.config.my.js"
  这两种配置方法均可以修改默认配置文件名。先在webpack.config.my.js写一段基本的配置:node

//webpack是用node写的
let path = require('path');
module.exports = {
    mode: 'development', //模式 生产环境production 开发模式development
    entry: './src/index.js', //入口
    output: { //出口
        filename: 'bundle.[hash:8].js', //打包后的文件名,[hash]每次打包生成新的文件
        //__dirname以当前目录解析成绝对路径
        path: path.resolve(__dirname, 'dist'), ///path字段只接受绝对路径,所以须要一个node模块来辅助配置 path.resolve把相对路径解析成绝对路径 
        publicPath: 'http://www.help.com'//公共路径,打包出的资源文件会带着这个公共路径。
    }
} 
复制代码

Loader

  loader帮助咱们告诉浏览器遇到不能识别的模块应该怎么处理,前面咱们说过webpack默认配置只是别js模块,那么解析图片、css、less这些模块就须要引入loader。react

打包图片(file-loader,url-loader)

  图片引入有三种方式:jquery

  1. 在js中建立图片来引入,打包时要用到file-loader来解析图片地址加hash戳。
const logo = require('./01.jpg') //把图片引入,返回的结构是一个新的图片地址
const image = new Image();
console.log(logo); //用到file-loader 默认会在内部生成一张图片,到build目录下 把生成的图片的名字返回回来
image.src = logo;
document.body.appendChild(image)
复制代码

  配置以下:webpack

  • 使用file-loader:它解析静态资源,把原文件原封不动的拷贝一份放到打包后的文件夹下,而且把位置返回,上述logo的值就是该资源在打包后文件夹下的路径。通常可直接展现的文件用file-loader解析,好比图片,excel图表之类的。
{
    test: /\.(png|jpe?g|gif)$/,
    use: {
        loader: 'file-loader',
        options: {
            name:[name].[ext],
            outputPath: '/img/' //打包时另外生成一个img文件夹
        }
    }
},
复制代码
  • 使用url-loader:它是file-loader的增强版,咱们在它的配置项中能够设置一个limit值。若是文件体积小于该值,则文件被编码成Base64字符串直接引用,而不打包成一个新文件。这样能够减小一次http请求。若是文件大于该值,它的功能就至关于file-loader。这种方式会使打包后的文件体积变大,所以二者性能须要权衡。
{
    test: /\.(png|jpe?g|gif)$/,
    use: {
        loader: 'url-loader',
        options: {
            limit: 200 * 1024,//小于200k使用url-loader,大于200k使用file-loader
            outputPath: '/img/'
        }
    }
},
复制代码
  1. 在css中添加图片,打包时css-loader会将url('./01.jpg')解析为url(require('./01.jpg'))。
body {
    background: red;
    background: url('./01.jpg')
}
复制代码
  1. 在html中经过图片的img标签引入图片,打包时须要用到html-withimg-loader来解析图片地址。
<img src="./01.jpg" alt="">
复制代码

  配置以下:ios

{
    test: /\.html$/,
    use: 'html-withimg-loader'
}
复制代码
样式设置(css-loader,style-loader,less-loader)

  打包CSS文件,咱们须要用到两个loader,一个是style-loader,它负责处理css文件中的import、url()语法。style-loader之内联<style>的形式将样式都写到模版html的<head>头部中。打包LESS文件一样的套路,less-loader现将less转换成css。
  配置以下:nginx

module: {
    rules: [ 
        //loader的用法。字符串只用一个loader 多个loader须要用数组 loader的顺序 默认从右向左 从下往上执行
        {
            test: /\.css$/,
            use: [{
                loader: 'style-loader',
                options: {
                    insertAt: 'top'//插在最上面,让本身写在模版html<style>标签中的样式优先级较高
                }
            }, 'css-loader']
        },
        {
            test: /\.less$/,
            use: [{
                loader: 'style-loader',
                options: {
                    insertAt: 'top'
                }
            }, 'css-loader', 'less-loader']
        }
    ]
}
复制代码

  以下图所示,head标签下面的三个样式是分别在.css和.less文件中定义的样式,而<head>标签上面的一个样式是在模版html中本身设定的。 es6

CSS3为兼容自动加浏览器前缀(postcss-loader)

  用一个autoprefixer包和一个postcss-loader自动添加浏览器前缀,且这个插件是会更新的,之前transform在须要加上webkit前缀,但Chrome支持后postcss-loader就不会再给这个属性加上前缀了。

npm i postcss-loader autoprefixer -D
//在根目录建立postcss.config.js文件
module.exports = {
    plugins: [require('autoprefixer')]
}
//在rules css配置中新加入postcss-loader 
module: {
    rules: [ 
        //loader的用法。字符串只用一个loader 多个loader须要用数组 loader的顺序 默认从右向左 从下往上执行
        {
            test: /\.css$/,
            use: [{
                loader: 'style-loader',
                options: {
                    insertAt: 'top'//插在最上面,让本身写在模版html<style>标签中的样式优先级较高
                }
            }, 'css-loader']
        }
    ]
}
复制代码

Plugin

  Plugin能够在webpack运行到某个阶段的时候,帮助咱们作某些事情,相似于生命周期的概念。在某个时间点,须要某个机制完成一些事情。

生成模版html(HtmlWebpackPlugin插件)

  在咱们打包文件后,该插件会生成一个html模版,而且把打包后的其余文件在该模版中引用。生成的html模版的内容是咱们能够本身定义的。

const HtmlWebpackPlugin = require('html-webpack-plugin');  
plugins: [
    new HtmlWebpackPlugin({
        template: './src/index.html', //模版文件的位置
        filename: 'index.html', //打包出来html文件的名称
        minify: {
            removeAttributeQuotes: true, // 去除双引号
            collapseWhitespace: true, //变成一行
        },
        hash: true //添加一个hash戳
    })
],
复制代码
抽离CSS(MiniCssExtractPlugin插件)

  咱们打包时,把全部样式抽离出来生成一个CSS文件,在模版html文件中以link形式引入。

npm i mini-css-extract-plugin -D
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
plugins: [
    new MiniCssExtractPlugin({
        filename: 'main.css'
    })
]
module: {
    rules: [ 
        {
            test: /\.css$/,
            use: [
            MiniCssExtractPlugin.loader, //把style-loader换成MiniCssExtractPlugin.loader
            'css-loader', 
            'postcss-loader'
            ]
        },
        {
            test: /\.less$/,
            use: [
            MiniCssExtractPlugin.loader, 
            'css-loader', 
            'less-loader', 
            'postcss-loader'
            ]
        }
    ]
}
复制代码
压缩CSS和JS(OptimizeCSSAssetsPlugin插件、UglifyJsPlugin插件)

  进行到这一步会发现,生产模式下打包出来的main.css也没有被压缩,是由于用了MiniCssExtractPlugin这个插件不会压缩css,须要本身压缩。使用OptimizeCSSAssetsPlugin插件配置优化项,可是使用这个插件以后,css确实压缩了,但js又不会压缩了,所以还要用到UglifyJsPlugin再来压缩js。

npm i optimize-css-assets-webpack-plugin -D
npm i uglifyjs-webpack-plugin -D  
const UglifyJsPlugin = require("uglifyjs-webpack-plugin"); //压缩js
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin"); //压缩css
optimization: {
    minimizer: [
        new UglifyJsPlugin({
            cache: true, //缓存
            parallel: true, //并发压缩
            sourceMap: true // set to true if you want JS source maps
        }),
        new OptimizeCSSAssetsPlugin({})
    ]
}
复制代码
更新打包目录(CleanWebpackPlugin插件)

  在没有使用该插件以前,每次打包上一版本的文件会遗留在dist文件夹下,须要咱们手动删除。这个插件,在每次打包以前,先把以前的dist文件夹删除,打包生成新的dist目录。

npm install --save-dev clean-webpack-plugin
const {CleanWebpackPulgin} = require('clean-webpack-plugin')  
plugins: [
    new CleanWebpackPlugin()
]
复制代码

SourceMap

  源代码与打包后的代码的映射关系,帮助咱们定位错误在源代码中的位置。在devtool字段中配置,推荐的配置以下:

  • cheap :较快,只定位行,不定位列。
  • moudle :定位引入的第三方模块的错误。
  • eval :速度最快,包裹模块代码。
  • source-map :生成map,内容是源代码和打包后的代码的映射。
devtool:"cheap-module-eval-source-map" //开发环境
devtool:"cheap-module-source-map" //线上生产环境
复制代码

webpack-dev-server

  一个提高开发效率的利器,每次改完代码都要从新打包一次,刷新浏览器很是的麻烦。用webpack-dev-server搭建一个服务器,使得咱们不用真实的打包,而是在内存中打包,放置到devSever服务器上,以便咱们在开发时调试测试整个项目。
  下载webpack-dev-server:

npm i webpack-dev-server -D  
复制代码

  以后,先在package.json中配置scripts,"dev" : "webpack-dev-server --config webpack.config.my.js"。而后配置一下devServer中的一些配置项:

devServer: { //开发服务器的配置
    port: 8889, //端口号
    progress: true, //进度条
    contentBase: './dist', //指定了服务器资源的根目录,可是在开发过程不会真实打包
    compress: true, //启用 gzip 压缩
    open: true //自动打开浏览器
},
复制代码
mock

  联调期间,先后端分离,直接获取数据会跨域,上线后咱们使⽤用nginx转发,开发期间,webpack-dev-server就能够搞定这件事。
  咱们先启动服务器,mock一个接口:

const koa = require('koa')
const app = new koa()  

const Router = require('koa-router')
const router = new Router()  

router.get('/api/info', async (ctx, next) => {
   ctx.body = {
       username: 'zhunny',
       message: 'hello mock'
   }
   ctx.status = 200
})

app.use(router.routes())

app.listen(3000)
复制代码

  此时在咱们前端项目中请求该接口的数据,会存在跨域问题,咱们在dev-server中配置服务器代理:

axios.get('http://localhost:3000/api/info').then(res=>{
    console.log(res)
})
复制代码
devServer: { //开发服务器的配置
    port: 8889, //端口号
    progress: true, //进度条
    contentBase: './dist', //指定了服务器资源的根目录,可是在开发过程不会真实打包
    compress: true, //启用 gzip 压缩
    open: true, //自动打开浏览器
    proxy: {
       "/api": {
         target: "http://localhost:3000"
        }
    }
},
复制代码

  以后修改请求的接口:

axios.get('/api/info').then(res=>{
    console.log(res)
})
复制代码

转换es6语法及校验

  webpack自己能够处理ES6语法,可是有些浏览器对es六、es7或者是es8的语法还不能识别。出于兼容性的考虑,咱们会使用Babel来将ES6转换成ES5语法。

  • babel-loader: 是Babel于webpack通讯的桥梁。
  • @babel/core: Babel工具的核心代码库。
  • @babel/preset-env: ES6语法的转换规则。
npm i babel-loader @babel/core @babel/preset-env -D
{
    test: /\.js$/,
    exclude: /node_modules/, //排除该文件夹下的内容
    use: {
        loader: 'babel-loader',
        options: {
            presets: [
                '@babel/preset-env'
            ]
        }
    }
}
复制代码
@babel/polyfill

  固然只配置上述字段是不够的,到此步为止,一些es六、七、8新增的方法和类依然不能被识别。咱们还须要下载@babel/polyfill,它将es六、七、8中的语法特性打包放到浏览器中,至关于一个补丁包。
  它的基本使用方法是在入口js文件中引用:import '@babel/polyfill'。可是这种方法是引入整个补丁包,使得webpack打包后的体积变大。咱们能够对这点进行优化。移除在js文件中引用的@babel/polyfill,配置文件中添加useBuiltIns字段,对使用到的es六、七、8语法特性按需加载。

presets: [
    [
        '@babel/preset-env',
        {
            useBuiltIns: 'usage',//按需加载
            corejs: 2 //指定core的版本
        }
    ]
],
复制代码
@babel/plugin-transform-runtime

  当咱们开发组件库、工具库这些场景时,在js文件中引用@babel/polyfill就不合适了。由于@babel/polyfill以全局变量的方式注入,会形成全局污染。上面用的按需加载usage的方法也不会形成全局污染,可是这个字段还在试验阶段。咱们可使用闭包方式@babel/plugin-transform-runtime来代替。可是这种方式就不会对原型链上的某些方法进行转义,所以开发正常的业务场景就比较适合用polyfill,无所谓全局变量的影响,咱们不须要担忧某些原型链上的方法没有被转义。

npm i @babel/plugin-transform-runtime -D
npm i @babel/runtime -S  
{
    test: /\.js$/,
    exclude: /node_modules/, //排除该文件夹下的内容
    use: {
        loader: 'babel-loader',
        options: {
            plugins: [
                [
                '@babel/plugin-transform-runtime',
                {
                    absoluteRuntime:false,
                    corejs:2,
                    helpers:true,
                    regenerator:true,
                    useESMoudules:false
                }
                ]
            ]
        }
    }
}
复制代码
.babelrc

  Babel配置可能内容较多,咱们能够把options内容放到.babelrc中。

//.babelrc
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "usage", //按需加载 实验性的功能
        "corejs": 2
      }
    ],
    "@babel/preset-react"
  ]
}
复制代码
一些提案语法的补丁包

  一些es7的提案如class,则还须要用到@babel/plugin-proposal-class-properties,装饰器则须要用到@babel/plugin-proposal-decorators:

npm i @babel/plugin-proposal-class-properties -D npm i @babel/plugin-proposal-decorators -D {
    test: /\.js$/,
    use: {
        loader: 'babel-loader',
        options: {
            presets: [
                '@babel/preset-env'
            ],
            plugins: [
                ['@babel/plugin-proposal-decorators', { "legacy": true }],
                ['@babel/plugin-proposal-class-properties', { "loose": true }],
                ['@babel/plugin-transform-runtime'] //generator
            ]
        }
    }
}
复制代码
校验

  js语法的校验用到了eslint以及eslint-loader,他的官网为https://eslint.org,eslint在使用时须要配置一个.eslint.json的规则文件放在根目录,具体配置项见官网。

npm i eslint eslint-loader -D
{
    test: /\.js$/,
    use: {
        loader: 'eslint-loader',
        options: {
            enforce: 'pre' //在普通loader以前执行
        }
    },
}
复制代码

全局变量的引入

  引入全局变量有三种方式,假如要在全局引入jquery库:

  1. 使用内联loader expose-loader将jquery暴露到window属性上。
import $ from 'jquery'
require(expose-loader)
console.log(window.$)
复制代码
  1. 使用webpack.providePlugin插件将$注入到每一个模块。
new webpack.providePlugin(
    {$:'jquery'}
)
复制代码
  1. 引入jquery的cdn。

参考

webpack官方网站
Babel官方网站

相关文章
相关标签/搜索