webpack由浅入深——(webpack基础配置)

webpack的做用

构建就是把源代码转换成发布到线上的可执行JavaScrip、CSS、HTML代码,包括以下内容:css

  • 代码校验:在代码被提交到仓库前须要校验代码是否符合规范,以及单元测试是否经过。
  • 代码转换:TypeScript 编译成 JavaScript、SCSS 编译成 CSS 等。
  • 模块合并:在采用模块化的项目里会有不少个模块和文件,须要构建功能把模块分类合并成一个文件。
  • 代码分割:提取多个页面的公共代码、提取首屏不须要执行部分的代码让其异步加载。
  • 文件优化:压缩 JavaScript、CSS、HTML 代码,压缩合并图片等。
  • 自动刷新:监听本地源代码的变化,自动从新构建、刷新浏览器。
  • 自动发布:更新完代码后,自动构建出线上发布代码并传输给发布系统。

webapck核心概念

  • Entry:入口,Webpack 执行构建的第一步将从 Entry 开始,可抽象成输入。
  • Output:输出结果,在Webpack通过一系列处理并得出最终想要的代码后输出结果。
  • Module:模块,在 Webpack里一切皆模块,一个模块对应着一个文件。Webpack会从配置的 Entry 开始递归找出全部依赖的模块。
  • Chunk:代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。
  • Loader:模块转换器,用于把模块原内容按照需求转换成新内容。
  • Plugin:扩展插件,在Webpack构建流程中的特定时机注入扩展逻辑来改变构建结果或作你想要的事情。

webpack工做过程

  1. Webpack 启动后会从Entry里配置的Module开始递归解析 Entry 依赖的全部 Module。
  2. 每找到一个 Module, 就会根据配置的Loader去找出对应的转换规则,对Module 进行转换后,再解析出当前 Module 依赖的 Module。 这些模块会以 Entry 为单位进行分组,一个 Entry 和其全部依赖的 Module 被分到一个组也就是一个 Chunk。
  3. 最后 Webpack 会把全部 Chunk 转换成文件输出。 在整个流程中 Webpack 会在恰当的时机执行Plugin 里定义的逻辑。

webpack系列文章

  1. webpack由浅入深——(webpack基础配置)
  2. webpack由浅入深——(webpack优化配置)
  3. webpack由浅入深——(tapable)
  4. webpack由浅入深——(webapck简易版)
  5. webpack由浅入深——(ast、loader和plugin)

初始化项目

首先确保电脑已经安装了nodejs,推荐采用nvm的形式进行安装,这样就不用配置环境变量或者建立软连接。html

mkdir webpack-learn //经过命令行建立文件夹
cd webpack-learn    //打开建立的文件夹
npm init -y //初始化一个项目
npm install webpack webpack-cli -D //本地安装webpack和webpack-cli
mkdir src   //建立src目录来存放源代码
mkdir dist  //建立dist目录来存放打包后的代码
复制代码

在src目录下建立index.js前端

let str = require('./a');
console.log(str);
复制代码

在src目录下建立a.jsnode

module.exports='webpack-learn'
复制代码

在dist目录下建立index.html文件react

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<div id="root"></div>
<script src="bundle.js"></script>
</body>
</html>
复制代码

利用webpack进行打包,关于commonjs不清楚的,请参考require()源码解读jquery

npx webpack -- mode development //以默认以开发模式进行打包
复制代码

mode-d

Entry(入口)和Output(输出)

从上面的代码中能够看到文件打包到了dist/main.js中,因此须要进行相应的配置webpack.config.js来自定义结果文件名。webpack

单入口

  • 字符串形式
const path=require('path');
module.exports={
    entry: './src/index.js',    //入口
    output: {
        path: path.resolve(__dirname,'dist'),   //出口,绝对路径
        filename:'bundle.js'
    },
    module: {},     //配置loader
    plugins: [],    //配置插件
    devServer: {}   //配置本地服务
    resolve:{},     //配置解析文件路径等
}
复制代码
  • 数组形式
//index.js
console.log('hello');
复制代码
//a.js
console.log('world')
复制代码
const path=require('path');
module.exports={
    //index和a之间没有依赖关系,只是单纯的合并
    entry: ['./src/index.js','./src/a.js'],
    output: {
        path: path.resolve(__dirname,'dist'),  
        filename:'bundle.js'
    },
    module: {},     
    plugins: [],    
    devServer: {}   
    resolve:{},
}
复制代码

多入口

//a.js
let str =require('./c')
console.log(str);
复制代码
//b.js
let str =require('./d')
console.log(str);
复制代码
//c.js
module.export = 'hello'
复制代码
//d.js
module.export = 'world'
复制代码
const path=require('path');
module.exports={
    //多入口拆分功能,能够两个页面分别引用一个,也能够一个页面引用多个
    //配合后面的html-webpack-plugin使用
    entry: {
        pageA:'./src/a',
        pageB:'./src/b'
    },
    output: {
        path: path.resolve(__dirname,'dist'), 
        //带有哈希值的文件名exp:pageA.fa112c6二、pageB.fa112c62
        filename:'[name].[hash:8].js'    
    },
    module: {},     
    plugins: [],    
    devServer: {}   
    resolve:{},
}
复制代码

自动生成页面

如今页面是手动建立到dist目录下的,一个页面还好,若是存在多个页面,手动创造html的代价是很大的,能够利用html-webpack-plugin来自动建立页面:web

npm install html-webpack-plugin -D
复制代码
//src/index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
</body>
</html>
复制代码
const path = require('path');
const webpack =require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
    entry:{
        pageA:'./src/a',
        pageB:'./src/b'
    },
    output: {
        path: path.resolve(__dirname,'dist'), 
        filename:'[name].[hash:8].js'    
    },
    module: {},
    plugins: [
        //在实际项目中,经过读取须要建立的页面信息,遍历建立实例
         new HtmlWebpackPlugin({
            template:'./src/index.html',
            filename:'pageA.html',
            chunks:['pageA,pageB'],   //数组,能够放多个chunk
            //页面资源携带哈希值exp:pageA.fa112c62?r2452567&4124
            //中间哈希一直都有,这个后面的哈希只在页面引用添加在页面中
            hash:true,  
            minify:{
                collapseWhitespace:true, //压缩代码,去除空格和换行
                removeAttributeQuotes:true//压缩代码,去除属性双引号    
            }
        }),
        new HtmlWebpackPlugin({
            template:'./src/index.html',
            filename:'pageB.html',
            chunks:['pageB'],
            hash:true,
            minify:{
                collapseWhitespace:true,
                removeAttributeQuotes:true
            }
        })
    ],
    devServer: {},
    resolve:{},
}
复制代码

配置script

因为每次打包都须要执行npx webpack --mode development,因此能够在package.json中进行配置:express

"scripts": {
    //等价于webpack --config webpack.config.js --mode development
    //默认是执行webpack.config.js,能够根据env来配置执行不一样的文件
    "start": "webpack --mode development"
  },
复制代码

devServer(本地服务器)

首先必须的安装插件webpack-dev-derver:npm

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

修改script:

"scripts": {
    "dev":"webpack-dev-server --mode development"
    "build": "webpack --mode development"
 },
复制代码

修改配置文件:

//index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id='app'></div>
</body>
</html>
复制代码
//a.js
module.exports = 'hello'
复制代码
//index.js
let str = require('./a');
document.getElementById('app').innerHTML = str;
复制代码
const path=require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports={
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname,'dist'), 
        filename:'bundle.js'
    },
    module: {}, 
    plugins: [
         new HtmlWebpackPlugin({
            template:'./src/index.html',
            filename:'index.html',
            hash:true,
            minify:{
                collapseWhitespace:true,
                removeAttributeQuotes:true
            }
        })
    ],
    devServer: {
        //devServer会在打包的时候把生成的文件放在内存中,
        //而且是以根目录为参照目录而不是dist目录,因此须要修改
        contentBase:'./dist',
        port:'3000',
    } 
    resolve:{},
}
复制代码

经过浏览器,输入localhost:3000就能够看到

本地服务
经过修改a.js中module.exports = 'hello1',能够看到
热更新
这样会有一个问题,就是只要源代码中存在改动,就会刷新页面。假如在react本地开发的时候,有不少组件,其中一个组件的代码修改了,不但愿全部的组件都更新,能够利用热更新来解决:

const path = require('path');
const webpack =require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports={
    entry:'./src/index.js',
    output: {
        filename:'bundle.js',
        path: path.resolve(__dirname,'dist')
    },
    module: {},
    plugins: [
        //使用热更新插件
        new webpack.HotModuleReplacementPlugin();
        new HtmlWebpackPlugin({
            template:'./src/index.html',
            filename:'index.html',
            hash:true,
            minify:{
                collapseWhitespace:true,
                removeAttributeQuotes:true
            }
        })
    ],
    devServer: {    
        contentBase:'./dist',
        port:'3000',
        hot:true    //热更新开关,使用websocket来通知页面更新
    },
    resolve:{},
}
复制代码
//index.js
let str = require('./a');
document.getElementById('app').innerHTML = str;

//这里必须加这段,否则的话,仍是没有办法使用热更新
if(module.hot){
    module.hot.accept();
}
复制代码

配置proxy

devServer: {    
        contentBase:'./dist',
        port:'3000',
        hot:true,
        proxy:{
            target:'http://xxxxx'   //代理的服务器
            pathRewirte:{
                '/xxx':'/yyy'
            }
        }
    },
复制代码
  • webpack服务集成到本身的本地服务
// 把webpack-dev-server的配置引入到本身的本地服务
const express = require('express');
const webpackDevMiddleware = require('webpack-dev-middleware');
// 引入webpack配置文件
const config = require('./webpack.config');
let app = express();

cosnt webpack = require('webpack');
let compiler = webpack(config);     //用webpack进行编译
app.use(webpackDevMiddleware(compiler)); 

app.listen(3000);
复制代码

module中的loader

webpack每找到一个Module,就会根据配置的Loader去找出对应的转换规则,让js能过编译css、ejs、jsx和图片的各类格式。

css相关loader

  • style-loader 和css-loader
//index.css
body{
    background:red;
    border-radio:4px;
}
复制代码
//通常前端代码使用import引入模块,node服务使用require引入模块
import str from './a'
import './index.css'
document.getElementById('app').innerHTML=str

if(module.hot){
    module.hot.accept();
}
复制代码

执行npm run build以后发现出现如下报错,说明须要配置loader:

loader报错
首先须要安装loader

npm install css-loader style-loader -D
复制代码
const path = require('path');
const webpack =require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
    entry:{
        index:'./src/index'
    },
    output: {
        filename:'[name].[hash:8].js',
        path: path.resolve(__dirname,'dist')
    },
    module: {
        rules:[
            {
                test:/\.css$/,
                //多个loaders用数组,loader的形式有字符串和对象两种
                //字符串形式:'xxx?option1!yyyy'
                //对象形式{loader:'xxx',options:{option1:yyyy}}
                //less-laoder将less转化为css,css-loader解析css,style-loader插入到style标签中
                use:['style-oader','css-loader']    
            }
        ]
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin(),
        new HtmlWebpackPlugin({
            template:'./src/index.html',
            filename:'index.html',
            chunks:['index']
        })
    ],
    devServer: {    
        contentBase:'./dist',
        port:'3000',
        hot:true
    },
    resolve:{},
}
复制代码

用npm run dev启动服务能够看到效果,可是这样有一个问题,css样式采用的内嵌式,最好能抽离出来使用外链式引入,能够使用插件mini-css-extract-plugin:

npm install mini-css-extract-plugin -D
复制代码
const path = require('path');
const webpack =require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
    entry:{
        index:'./src/index'
    },
    output: {
        filename:'[name].[hash:8].js',
        path: path.resolve(__dirname,'dist')
    },
    module: {
        rules:[
            {
                test:/\.css$/,
                use:[{
                    loader:MiniCssExtractPlugin.loader,
                    options:{
                        //将css中的路经前面添加,background:url('xxxx')
                        //http://ssss/xxxxx
                        publicPath:'http://sssss'    
                    }
                },
                'css-loader'
                ]    
            }
        ]
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin(),
        new MiniCssExtractPlugin({
            filename:index.css,
        }),
        new HtmlWebpackPlugin({
            template:'./src/index.html',
            filename:'index.html',
            chunks:['index']
        })
    ],
    devServer: {    
        contentBase:'./dist',
        port:'3000',
        hot:true
    },
    resolve:{},
}
复制代码
  • postcss-loader和autoprefixer插件

因为前端写的代码要兼容各类浏览器,css属性为了兼容各大浏览器,每每须要添加各类前缀,可是同一个属性写多份,这个工做量仍是比较大的,有一个postcss-loader能够配合autoprefixer插件使用

//index.css
body{
    background:red;
    transform: rotate(0,0,100deg);
}
复制代码
npm install postcss-loader autoprefixer -D
复制代码
//建立postcss.config.js
module.exports ={
    plugins:[
        require('autoprefixer')
    ]
} 
复制代码
const path = require('path');
const webpack =require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
    entry:{
        index:'./src/index'
    },
    output: {
        filename:'[name].[hash:8].js',
        path: path.resolve(__dirname,'dist')
    },
    module: {
        rules:[
            {
                test:/\.css$/,
                use:[
                {loader:MiniCssExtractPlugin.loader},
                'css-loader',
                'postcss-loader'    //添加css前缀处理laoder
                ]    
            }
        ]
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin(),
        new MiniCssExtractPlugin({
            filename:index.css,
        }),
        new HtmlWebpackPlugin({
            template:'./src/index.html',
            filename:'index.html',
            chunks:['index']
        })
    ],
    devServer: {    
        contentBase:'./dist',
        port:'3000',
        hot:true
    },
    resolve:{},
}
复制代码

图片相关的loader

项目中引入图片的方式有三种:

  1. 经过js引入
//index.js
import './index.css'
import jpg from './1.jpg'
let img = new Image();
img.src=jpg;
document.body.appendChild(img);
if(module.hot){
    module.hot.accept();
}
复制代码
  1. 经过css引入
//index.css
body{
    background: url('./1.jpg') no-repeat right;
}
复制代码
  1. 经过img的src属性引入
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <img src="./1.jpg" alt="">
</body>
</html>
复制代码

处理前两种引入图片的方式须要使用file-loader和url-loader,其中url-laoder内部会引用file-loader,它们的做用就是解析js和css中的图片连接而后将图片变成base64。后一种引入图片的方式须要使用html-withimg-loader。

npm install file-loader url-loader html-withimg-loader -D
复制代码
const path = require('path');
const webpack =require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
    entry:{
        index:'./src/index'
    },
    output: {
        filename:'[name].[hash:8].js',
        path: path.resolve(__dirname,'dist')
    },
    module: {
        rules:[
            {
                test:/\.css$/,
                use:[{
                    loader:MiniCssExtractPlugin.loader,
                },'css-loader']
            },
            {
                test:/\.jpg|png/,
                use:{
                    loader:'url-loader',
                    options:{
                        //大于8k的图片会打包到dist目录下,小于8k的图片会生成base64插入到引用的地方
                        //base64会使资源变大1/4,可是base64无需发送请求,资源比较小时使用base64最佳
                        limit:8*1024   
                    }
                }
            },
            {
                test:/\.html$/,
                use:'html-withimg-loader'
            }
        ]
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin(),
        new MiniCssExtractPlugin({
            filename:index.css,
        }),
        new HtmlWebpackPlugin({
            template:'./src/index.html',
            filename:'index.html',
            chunks:['index']
        })
    ],
    devServer: {    
        contentBase:'./dist',
        port:'3000',
        hot:true
    },
    resolve:{},
}
复制代码

img-loader

js相关的loader

因为ES6的简洁和API扩展,有不少开发者使用ES6进行开发,可是因为浏览器的品牌和版本的不一样,就出现了开发时使用ES6而后赞成转化成ES5的状况。这时候就须要使用babel对ES6+的语法进行转译,关于babel的各类配置能够参考对babel-transform-runtime,babel-polyfill的一些理解

npm install babel-core babel-loader babel-preset-env babel-preset-stage-0 babel-plugin-tranform-runtime -D
复制代码
//建立.babelrc文件
//preset中包含了一组用来转换ES6+的语法的插件,可是还不转换新的API
//如需使用新的API,例如set(),还须要使用对应的转换插件或者polyfill(填充库)
{
    presets:{
        'env',      //环境变量,根据不一样浏览器环境而对应的转码
        'stage-0'   //转译ES6+(0 > 1 > 2 > 3 > 4)
    }
    plugins:{
        'tranform-runtime'
    }
}
复制代码
const path = require('path');
const webpack =require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
    entry:{
        index:'./src/index'
    },
    output: {
        filename:'[name].[hash:8].js',
        path: path.resolve(__dirname,'dist')
    },
    module: {
        rules:[
            {
                test:/\.css$/,
                use:[{
                    loader:MiniCssExtractPlugin.loader,
                },'css-loader']
            },
            {
                test:/\.jpg|png/,
                use:{
                    loader:'url-loader',
                    options:{
                        limit:8*1024   
                    }
                }
            },
            {
                test:/\.html$/,
                use:'html-withimg-loader'
            },
            {   //使用babel-loader
                test:/\.js$/,
                use:'babel-loader',
                exclude:/node_modules/  //排除编译ndoe——modules   
            }
        ]
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin(),
        new MiniCssExtractPlugin({
            filename:index.css,
        }),
        new HtmlWebpackPlugin({
            template:'./src/index.html',
            filename:'index.html',
            chunks:['index']
        })
    ],
    devServer: {    
        contentBase:'./dist',
        port:'3000',
        hot:true
    },
    resolve:{},
}
复制代码

Plugin(插件)

plugin和loader的区别在于loader只在编译module时执行,而plugin可能在webapck工做流程的各个阶段执行。

clean-webpack-plugin

清除dist目录

module.exports = {
    mode:'production',  生产环境会自动压缩   
    entry:{
        index:'./src/index'
    },
    output: {
        filename:'[name].[hash:8].js',
        path: path.resolve(__dirname,'dist')
    },
    module: {
        rules:[
            {
                test:/\.css$/,
                use:[{
                    loader:MiniCssExtractPlugin.loader,
                },'css-loader']
            },
            {
                test:/\.jpg|png/,
                use:{
                    loader:'url-loader',
                    options:{
                        limit:8*1024   
                    }
                }
            },
            {
                test:/\.html$/,
                use:'html-withimg-loader'
            },
            {   
                test:/\.js$/,
                use:'babel-loader',
                exclude:'/node_modules/'
            }
        ]
    },
    plugins: [
        new CleanWebpackPlugin(['dist/*.*']),   //清空dist目录
        new webpack.HotModuleReplacementPlugin(),
        new MiniCssExtractPlugin({
            filename:index.css,
        }),
        new HtmlWebpackPlugin({
            template:'./src/index.html',
            filename:'index.html',
            chunks:['index']
        })
    ],
    externals:{
        'jquery':'$'
    }
    devServer: {    
        contentBase:'./dist',
        port:'3000',
        hot:true
    },
    resolve:{},
}
复制代码

uglifyjs-webpack-plugin和optimize-css-assets-webpack-plugin

module.exports = {
    mode:'production',  生产环境会自动压缩   
    entry:{
        index:'./src/index'
    },
    output: {
        filename:'[name].[hash:8].js',
        path: path.resolve(__dirname,'dist')
    },
    optimization:{
        minimizer:{
            new UglifyJSPlugin({    //压缩js
                cache:true,
                parallel:ture,  //并行压缩
                sourthMap:true, //启动sourthMap
            }) ,
            new OptimizeCssAssetsPlugin()   //压缩css
        }
    },
    module: {
        rules:[
            {
                test:/\.css$/,
                use:[{
                    loader:MiniCssExtractPlugin.loader,
                },'css-loader']
            },
            {
                test:/\.jpg|png/,
                use:{
                    loader:'url-loader',
                    options:{
                        limit:8*1024   
                    }
                }
            },
            {
                test:/\.html$/,
                use:'html-withimg-loader'
            },
            {   
                test:/\.js$/,
                use:'babel-loader',
                exclude:'/node_modules/'
            }
        ]
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin(),
        new MiniCssExtractPlugin({
            filename:index.css,
        }),
        new HtmlWebpackPlugin({
            template:'./src/index.html',
            filename:'index.html',
            chunks:['index']
        })
    ],
    externals:{
        'jquery':'$'
    }
    devServer: {    
        contentBase:'./dist',
        port:'3000',
        hot:true
    },
    resolve:{},
}
复制代码

经常使用插件

  • html-webpack-plugin: 建立html并插入script标签
  • autoprefixer: 给cssh加前缀
  • mini-css-extract-plugin: 抽离css样式link到html
  • webpack-dev-server: 启动webpack服务
  • webpack-dev-middleware:webpack服务集成到本地的服务
  • uglifyjs-webpack-plugin:压缩js
  • optimize-css-assets-webpack-plugin:压缩css
  • clean-webpack-plugin:清空目录

结语

学习webpack的过程是从官方文档开始学习的,照着敲了一遍,而后搜索相关的内容。

相关文章
相关标签/搜索