webpack4.X 实战(二):从0开始搭建一个 简单的前端项目

3,296 阅读10分钟

一、项目说明

  • 这是一个 极简的前端项目,适合新手入门,简述如下(有详细说明)

    • 启动本地服务 webpack-dev-server

    • 处理资源路径

    • 处理 css、scss、less

    • css3 属性前缀

    • 分离css、压缩分离后的css、净化分离后的css

    • babel转换(babel7.X)

二、项目初始化

  • 新建项目根目录 文件夹 webpack_demo_basic

  • 进入终端执行 npm init -y

    生成 package.json 文件,如下

  • 初始化项目文件,目录如下

    src/css/index.css 文件:css样式文件

    src/less/less 文件:less样式文件

    src/scss/index.scss 文件:scss样式文件

    src/index.html 文件:项目html

    src/index.js 文件:项目入口js

    .babelrc 文件:项目babel配置文件

    .gitignore 文件:git忽略配置文件(使用 Git 版本控制时,需要配置)

    package.json 文件:定义了项目所需的模块,以及项目的配置信息

    webpack.config.js 文件:webpack配置文件

  • 安装 webpack依赖

    npm i webpack@4.28.3  webpack-cli@3.2.0  -D 
    
  • webpack 基础配置

    // webpack.config.js 文件
    
    const path = require('path');
    
    
    module.exports = {
        mode: 'development',        // 开发模式
    
        entry: './src/index.js',    // 打包后输出的文件名 为 main.js
        /**
         * 或
         * 
         * entry: {
         *    index: './src/index.js'
         * }
        */
    
        output: {
            path: path.resolve(__dirname, 'dist'),  // 打包后项目 输出到项目根目录下 dist 文件夹 
            filename: '[name].js'                   // 输出的 入口JS文件名称
        },
        
        // loader 相关配置
        module: {
            rules: []
        },
    
        // 插件 相关配置
        plugins: []
    };
    

三、配置/生成 页面 html

  • 插件:html-webpack-plugin

    根据模板 生成页面html

    自动引入 JS 等外部资源文件

    设置 title 、mate 等标签内容

    优化html(压缩、缓存、转换等)

  • 安装

    npm i html-webpack-plugin@3.2.0 -D
    
  • 配置

    // webpack.config.js 文件
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    
    
    module.exports = {
        plugins: [
            new HtmlWebpackPlugin({
                filename: 'index.html',         // 文件名; 默认是index.html
                template: './src/index.html',   // 指定模板html文件
    
                title: '11111',                 // html文件 title标签的内容
                inject: true,                   // 自动引入JS脚本的位置,默认值为 true(通常默认值即可满足条件)
                /*
                 * 此选项有如下四种值:
                 *
                 * true/'body': JS脚本在 body标签底部 引入
                 * 'head': JS脚本在 bead标签底部 引入
                 * false: 不插入任何JS(几乎不会用到)
                */
    
    
                // 如下 通常在生产模式下 才配置
                hash: true,                     // 默认值为false, 值为true时,html 引入的脚本、scss都加hash值(清除缓存)
                                                // 建议 生产环境下 设置为 true
                cache: true,                    // 默认值为true, 设置缓存
                                                // 建议 生产环境下 设置为 true
                minify: {                       // 建议 生产环境下 再配置此选项
                    minifycss: true,            // 压缩css
                    minifyJS: true,             // 压缩JS            
                    minifyURLs: true,           // 压缩URL
                    removeComments: true,               // 去掉注释
                    removeAttributeQuotes: true,        // 去掉标签上 属性的双引号
                    collapseWhitespace: true,           // 去掉空行
                    removeRedundantAttributes: true,    // 去掉多余的属性
                    removeEmptyAttributes: true         // 去掉空属性
                }
            })
        ]
    };
    
  • 配置说明

四、配置 本地服务

  • 服务器:webpack-dev-server

    一个小型的 Node.js Express服务器,程序开发时 本地运行项目

  • 安装

    npm i webpack-dev-server@3.1.14 -D
    
  • 配置

    // webpack.config.js 文件
    
    const path = require('path');
    
    module.exports = {
        devServer:{
            index: 'index.html',    // 服务器启动的页面(同 html-webpack-plugin 中 filename 选项); 默认值为 index.html
            port: 3000,             // 指定端口号; 默认 8080
            host: 'localhost',      // 指定host; 默认 localhost
            
            /*
            *   或 
            *   host: '0.0.0.0',        // 可 通过IP 访问,也可以通过 localhost 访问
            *   useLocalIp: true,       // browser open with your local IP
            */
    
            
            open: true,             // 启动本地服务后,自动打开页面
            compress: true,         // 是否启用 gzip 压缩
            overlay: true,          // 编译器错误或警告时, 在浏览器中显示全屏覆盖; 默认false
            progress: true,         // 是否将运行进度输出到控制台; 默认 false
    
            contentBase: path.resolve(__dirname, 'dist'),  // 告诉服务器从哪里提供内容。只有在你想要提供静态文件时才需要
    
            // 精简 终端输出(本地运行时)
            stats: {
                modules: false,
                children: false,
                chunks: false,
                chunkModules: false
            }
        }
    };
    
  • 配置说明

    • webpack-dev-server 详细配置

    • 支持热更新:检测到项目中修改的代码,立即在浏览器中自动更新

      新手需知道:热更新仅限于项目代码;webpack 相关的配置不会参与热更新

    • 精简终端输出

      • 关于 精简终端输出 详细讲解见之后的文章

      • 小伙伴们可以对比下,本地运行项目时,有无 stats 选项,终端输出的变化

    • 热替换

      • 场景:建议开发环境下使用

      • 好处:局部加载 页面有改动的地方;加快开发编译速度

      • 关于 热替换 详细讲解见之后的文章

五、配置 npm script

  • package.json 中配置相关指令,实现 本地运行、打包项目

    终端执行 npm run xxx,就会执行 package.json 里script对应的 xxx 指令

  • 本地启动项目

    • 指令配置

      // packgae.json 文件
      
      {
        "scripts": {
          "dev": "webpack-dev-server"
        }
      }
      
    • 说明

      • --progress--open
      {
          "scripts": {
          "dev": "webpack-dev-server --progress --open"
          }
      }
      
      
      // --progress 代表: 本地启动项目终端显示进度
      // --open 代表: 运行后浏览器自动打开页面
      // 这两个和 webpack-dev-server 服务器的配置选项 progress、open 达到的效果一致,无需重复配置
      
      • --config
      {
          "scripts": {
              "dev": "webpack-dev-server --config webpack/config.js"
          }
      }
      
      // "dev": "webpack-dev-server"` 默认去找 根目录下名为 `webpack.config.js` 的webpack配置文件,进行打包、运行
      // 如果有场景要自定义 webpack 配置文件名称/路径,可以使用 --config 指定 webpack 配置文件,如上
      
    • 项目根目录下,终端执行 npm run dev 即可本地运行项目

      浏览地址 http://localhost:3000/

  • 打包项目

    • 指令配置
    // packgae.json
    
    {
      "scripts": {
        "build": "webpack"
      }
    }
    
    • 说明
      • 如上打包会按照 webpack4.X 默认打包机制 进行打包

      • 后续文章会讲解 如何自定义打包

    • 项目根目录下,终端执行 npm run build 即可打包项目,输出到 根目录的 dist 文件夹

六、配置 资源路径

1. 解决 项目 中的路径问题

  • loader:url-loader、file-loader

    解决项目中的路径问题(不限于 css)

    让 webpack 识别 图片、视频、字体 等资源文件

  • url-loader 是 file-loader 的一个超集

    低版本的 url-loader 中封装了 file-loader,url-loader 的运行 不会依赖file-loader

    高版本的 url-loader 在使用时,需要额外安装 file-loader

  • url-loader 的 file-loader 区别

    • url-loader

      解决项目中的路径问题

      将 小体积的资源 转成 base64

      让 webpack 识别 资源文件

    • file-loader

      解决项目中的路径问题

      让 webpack 识别 资源文件

  • file-loader 原理浅析

    众所周知,webpack 会将各个模块打包成一个文件,最终我们样式中的 url 路径是相对入口html页面的,而不是相对于原始css文件所在的路径,这就会导致图片引入失败

    file-loader可以解析项目中的url(不限于css),会根据配置 将图片拷贝到相应的路径,打包时会根据规则修改引入路径

  • 安装

    npm i url-loader@1.1.2  file-loader@3.0.1  -D
    
  • 配置

    // webpack.config.js 文件
    
    module.exports = {
        module: {
            rules: [{
                test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
                use: [{
                    loader: 'url-loader',
                    options: {
                        limit: 8192,     // 文件体积小于 8192kb 时,将被转为 base64 资源
                        name: '[name].[ext]',
                        outputPath: 'static/assets/'    // 资源 输出路径
                    }
                }]
            }, {
                test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
                use: [{
                    loader: 'url-loader',
                    options: {
                        name: '[name].[ext]',
                        outputPath: 'static/assets/'    // 资源 输出路径
                    }
                }]
            }, {
                test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
                use: [{
                    loader: 'url-loader',
                    options: {
                        name: '[name].[ext]',
                        outputPath: 'static/assets/'    // 资源 输出路径
                    }
                }]
            }]
        }
    };
    
  • 说明

    • url-loader 详细配置

    • 以配置中 options.outputPath 为例,资源将被输出到 根目录下 static/assets 文件夹中

2. 解决 html 中的路径问题

  • loader:html-withimg-loader

    解决 html 中的路径问题,如 img 标签的路径问题

    webpack 建议所有图片都使用背景图的方式,所以默认情况下 在html中使用 img 标签,会显示路径错误

  • 安装

    npm i html-withimg-loader@0.1.16 -D
    
  • 配置

    // webpack.config.js 文件
    
    module.exports = {
        module: {
            rules: [{
                test: /\.(htm|html)$/,
                 use:[{
                    loader: 'html-withimg-loader'
                 }] 
            }]
        }
    };
    

七、配置 css相关

1. 识别 css

  • loader: css-loader style-loader

    webpack 默认只识别JS文件,要解析打包css 必须安装相应的loader

  • 安装

    npm i style-loader@0.23.1 css-loader@2.1.0 -D
    
  • 配置

    // webpack.config.js 文件
        
    module.exports = {
        module: {
            rules: [{
                test:/\.css$/,
                use: ['style-loader','css-loader'] 
            }]
        }
    };
    

2. 识别 scss

  • loader 相关: sass-loader、node-sass

    作为css预编译语言,需要 相应的loader进行解析

  • 安装

    npm i node-sass@4.11.0  sass-loader@7.1.0  -D
    
  • 配置

    // webpack.config.js 文件
        
    module.exports = {
        module: {
            relus: [{
                test: /\.scss$/,
                use: ['style-loader','css-loader','sass-loader'] // 编译顺序从右往左
            }]
        }
    };
    

3. 识别 less

  • loader 相关: less-loader、less

    作为css预编译语言,需要 相应的loader进行解析

  • 安装

    npm i less@3.9.0  less-loader@4.1.0  -D
    
  • 配置

    // webpack.config.js 文件
        
    module.exports = {
        module: {
            relus: [{
                test: /\.less$/,
                use: ['style-loader','css-loader','less-loader'] // 编译顺序从右往左
            }]
        }
    };
    

4. 自动添加 css3属性前缀

  • loader 相关: postcss-loader、autoprefixer

    针对css3新增的属性,并不是所有浏览器都支持

    为兼容不同内核的浏览器,增加属性前缀(与内核相对应)-webkit, -ms, -o, -moz

  • 安装

    npm i postcss-loader@3.0.0  autoprefixer@9.4.4 -D
    
  • 配置

    // 项目根目录新建 文件postcss.config.js,其中配置如下
    
    module.exports = {
        plugins: [
            require('autoprefixer')({
                "browsers": [
                    "defaults",
                    "not ie < 11",
                    "last 2 versions",
                    "> 1%",
                    "iOS 7",
                    "last 3 iOS versions"
                ]
            })
        ]
    };
    
    // webpack.config.js 文件
    // 给 css文件 中的css3属性自动 加属性前缀
    
    
    module.exports = {
        module: {
            rules: [{
                test: /\.css$/,
                use: ['style-loader', 'css-loader', 'postcss-loader']
            }]
        }
    };
    
    // webpack.config.js 文件
    // 给 scss文件 中的css3属性自动 加属性前缀
    
    
    module.exports = {
        module: {
            rules: [{
                test: /\.scss$/,
                use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader'] // 编译顺序从右往左
            }]
        }
    };
    
    // webpack.config.js 文件
    // 给 css文件 中的less属性自动 加属性前缀
    
    
    module.exports = {
        module: {
            rules: [{
                test: /\.less$/,
                use: ['style-loader', 'css-loader', 'postcss-loader', 'less-loader'] // 编译顺序从右往左
            }]
        }
    };
    

5. 分离 css

  • 插件:mini-css-extract-plugin

    webpack 认为css应该被打包到JS中,以减少HTTP请求次数

    但有的开发团队,要求 抽离css单独打包

  • 安装

    npm i mini-css-extract-plugin@0.5.0 -D
    
  • 配置

    // webpack.config.js 文件,抽离css
    
    
    const MinicssExtractPlugin = require("mini-css-extract-plugin");
    
    module.exports = {
        module: {
            rules: [{
                test: /\.css$/,
                use: [MinicssExtractPlugin.loader, 'css-loader']
            }]
        },
        
        plugins: [
            new MinicssExtractPlugin({
                filename: "static/css/[name].[hash:7].css"
            })
        ]
    }
    
    // webpack.config.js 文件,抽离scss
    
    
    const MinicssExtractPlugin = require("mini-css-extract-plugin");
    
    module.exports = {
        module: {
            rules: [{
                test: /\.scss$/,
                use: [MinicssExtractPlugin.loader, 'css-loader', 'sass-loader']
            }]
        },
        
        plugins: [
            // 添加一处即可
            new MinicssExtractPlugin({
                filename: "static/css/[name].[hash:7].css"
            })
        ]
    }
    
    // webpack.config.js 文件,抽离less
    
    
    const MinicssExtractPlugin = require("mini-css-extract-plugin");
    
    module.exports = {
        module: {
            rules: [{
                test: /\.less$/,
                use: [MinicssExtractPlugin.loader, 'css-loader', 'less-loader']
            }]
        },
        
        plugins: [
            // 添加一处即可
            new MinicssExtractPlugin({
                filename: "static/css/[name].[hash:7].css"
            })
        ]
    }
    
  • 解决 css抽离后导致的路径问题

    很容易将 css抽离出来,但是抽离出来的css中属性 引用路径并不对(原因浅析:打包后分离出来的css 和 html 不在同一级目录下)

    解决方案:配置资源文件的输出路径(相对路径 改成 绝对路径)

    // webpack.config.js 配置文件
    
    
    let publicPath = '';
    const isProduction = false; // 是否是生产环境
    
    if (!isProduction) {
        publicPath = 'http://localhost:3000/static/assets';
    } else {
        // 将 publicPath 设置为线上发布地址
    }
    
    
    module.exports = {
        // loader 相关配置
        module: {
            rules: [{
                test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
                use: [{
                    loader: 'url-loader',
                    options: {
                        limit: 8192,                    // 文件体积小于 8192kb 时,将被转为 base64 资源
                        name: '[name].[ext]',
                        outputPath: 'static/assets/',   // 资源 输出路径
                        publicPath
                    }
                }]
            }, {
                test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
                use: [{
                    loader: 'url-loader',
                    options: {
                        name: '[name].[ext]',
                        outputPath: 'static/assets/',    // 资源 输出路径
                        publicPath
                    }
                }]
            }, {
                test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
                use: [{
                    loader: 'url-loader',
                    options: {
                        name: '[name].[ext]',
                        outputPath: 'static/assets/',    // 资源 输出路径
                        publicPath
                    }
                }]
            }]
        }
    };
    
    

6. 压缩 分离后的css

  • 插件:optimize-css-assets-webpack-plugin

    即使是生产模式下,分离后的css不会被压缩

    使用此插件对css进行压缩处理

  • 安装

    npm i optimize-css-assets-webpack-plugin@5.0.1 -D
    
  • 配置

    // webpack.config.js 文件
    
    const Optimizecss = require('optimize-css-assets-webpack-plugin');
    
    
    module.exports = {
        plugins: [
            new Optimizecss()
        ]
    }
    

7. 净化 分离后的css

  • 插件:purifycss-webpack、purify-css

    对于页面中没有使用到的css,打包时可以通过此插件进行清除

    限制:仅限于 清除分离后的css文件中 无用的样式

  • 安装

    npm i purifycss-webpack@0.7.0  purify-css@1.2.5 -D
    
  • 配置

    // webpack.config.js 文件
    const glob = require('glob');
    const PurifycssPlugin = require("purifycss-webpack");
    
    
    module.exports = {
        plugins: [
            new PurifycssPlugin({           
                paths: glob.sync(path.join(__dirname, 'src/*.html')),
            })
        ]
    };
    

八、配置 JS相关

1. babel 转换

  • 概述

    • 浏览器对 JavaScript 新语法、API、扩展语言的识别能力不同

    • babel 就是 JavaScript 语法编译器 之一

      • 主要将 ECMAScript 2015+ 的JS代码转换成 低版本浏览器可识别的 JS 代码

      • 将 JSX、TypeScript 等JS扩展语言 转换成 浏览器可识别的原生 JS

  • 安装

    npm i babel-loader@8.0.5  @babel/core@7.2.2  @babel/preset-env@7.2.3  @babel/plugin-transform-runtime@7.2.0  @babel/runtime@7.2.0  babel-plugin-transform-remove-console@6.9.4  @babel/plugin-syntax-dynamic-import@7.2.0 -D
    
  • 配置

    下面是 babel 7+ | babel-loader 8+ 的相关配置

    • 方式一:将 babel 的配置项抽离到 .babelrc 文件中(推荐)

      // webpack.config.js 文件
      const path = require('path');
      
      
      module.exports = {
          module: {
              rules: [{
                  test: /\.js$/,
                  use: ['babel-loader'],
                  exclude: /node_modules/, // 排除不要加载的文件夹
                  include: path.resolve(__dirname, 'src') // 指定需要加载的文件夹
              }]
          }
      };
      
      // .babelrc 文件中 配置
      
      {
          "presets": [
              [
                  "@babel/preset-env",            // 根据 preset-env 标准进行转换
                  {
                      "modules": false            // 不转换模块类型
                  }
              ]
          ],
          "plugins": [
              "@babel/plugin-transform-runtime",  // 来处理全局函数和优化babel编译
              "transform-remove-console"          // 打包时 移除 console 相关(开发环境下 不需要配置)
          ],
          "comments": false                       // 打包时 移除脚本中的注释 
      }
      
    • 方式二:直接在 webpack 配置项中 配置(不推荐)

      // webpack.config.js 文件
      const path = require('path');
          
      
      module.exports = {
          module: {
              rules: [{
                  test: /\.js$/,
                  use: [{
                      loader: 'babel-loader',
                      options: {
                          {
                              presets: [
                                  [
                                      "@babel/preset-env",            // 根据 preset-env 标准进行转换
                                      {
                                          "modules": false            // 不转换模块类型
                                      }
                                  ]
                              ],
                              plugins: [
                                  "@babel/plugin-transform-runtime",  // 来处理全局函数和优化babel编译
                                  "transform-remove-console"          // 打包时 移除 console 相关
                              ],
                              comments: false                       // 打包时 移除脚本中的注释 
                          }
                      }
                  }],
                  exclude: /node_modules/,                // 排除不要加载的文件夹
                  include: path.resolve(__dirname, 'src') // 指定需要加载的文件夹
              }]
          }
      };
      
  • 说明

2. 压缩 JS

九、webpack 配置文件

const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MinicssExtractPlugin = require("mini-css-extract-plugin");
const Optimizecss = require('optimize-css-assets-webpack-plugin');
const glob = require('glob');
const PurifycssPlugin = require("purifycss-webpack");



let publicPath = '';
const isProduction = false; // 是否是生产环境

if (!isProduction) {
    publicPath = 'http://localhost:3000/static/assets';
} else {
    // 将 publicPath 设置为线上发布地址
}


module.exports = {
    mode: 'development',

    entry: {
        index: './src/index.js'
    },

    output: {
        path: path.resolve(__dirname, 'dist'), // 打包后项目 输出到项目根目录下 dist 文件夹 
        filename: '[name].js'                  // 输出的 入口JS文件名称
    },

    // loader 相关配置
    module: {
        rules: [{
            test: /\.(htm|html)$/,
             use:[{
                loader: 'html-withimg-loader'
             }] 
        }, {
            test: /\.css$/,
            use: [MinicssExtractPlugin.loader, 'css-loader']
        }, {
            test: /\.scss$/,
            use: [MinicssExtractPlugin.loader, 'css-loader', 'sass-loader']
        }, {
            test: /\.less$/,
            use: [MinicssExtractPlugin.loader, 'css-loader', 'less-loader']
        }, {
            test: /\.js$/,
            use: ['babel-loader'],
            exclude: /node_modules/,                // 排除不要加载的文件夹
            include: path.resolve(__dirname, 'src') // 指定需要加载的文件夹
        }, {
            test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
            use: [{
                loader: 'url-loader',
                options: {
                    limit: 8192,                    // 文件体积小于 8192kb 时,将被转为 base64 资源
                    name: '[name].[ext]',
                    outputPath: 'static/assets/',
                    publicPath
                }
            }]
        }, {
            test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
            use: [{
                loader: 'url-loader',
                options: {
                    name: '[name].[ext]',
                    outputPath: 'static/assets/'    // 资源 输出路径
                }
            }]
        }, {
            test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
            use: [{
                loader: 'url-loader',
                options: {
                    name: '[name].[ext]',
                    outputPath: 'static/assets/'    // 资源 输出路径
                }
            }]
        }]
    },

    // 插件 相关配置
    plugins: [
        new HtmlWebpackPlugin({
            filename: 'index.html',         // 文件名; 默认是index.html
            template: './src/index.html'    // 指定模板html文件
        }),

        // 分离 css
        new MinicssExtractPlugin({
            filename: "static/css/[name].[hash:7].css"
        }),

        // 压缩分离后的 css
        new Optimizecss(),

        // 净化 css
        new PurifycssPlugin({           
            paths: glob.sync(path.join(__dirname, 'src/*.html')),
        })
    ],

    devServer:{
        index: 'index.html',    // 服务器启动的页面(同 html-webpack-plugin 中 filename 选项); 默认值为 index.html
        host: 'localhost',      // 指定host; 默认 localhost
        port: 3000,             // 指定端口号; 默认 8080

        open: true,             // 启动本地服务后,自动打开页面
        compress: true,         // 是否启用 gzip 压缩
        overlay: true,          // 编译器错误或警告时, 在浏览器中显示全屏覆盖; 默认false
        progress: true,         // 是否将运行进度输出到控制台; 默认 false

        contentBase: path.resolve(__dirname, 'dist'),  // 告诉服务器从哪里提供内容。只有在你想要提供静态文件时才需要

        // 精简 终端输出(本地运行时)
        stats: {
            modules: false,
            children: false,
            chunks: false,
            chunkModules: false
        }
    }
};