从负一开始构建一个基于webpack4的项目

601 阅读6分钟

前言

作为一个复制粘贴工程师,一直以来都是(可能只有我这样 o(╥﹏╥)o )

vue create hello-world
npx create-react-app hello-world

类似这样的脚手架命令一顿操作,什么Babel啊、Postcss、各种Loader、Eslint啊Uglifyjs都一把梭好了,然鹅到底这些是怎么配置的,不满足需求的时候怎么办呢? 这些脚手架都是基于Webpack(Webpack是什么?点我)的,最近在写原生JS项目,没有了脚手架就没有Babel、autoprefixer、Uglifyjs了,那谁给我转代码,谁给我加浏览器前缀、谁给我压缩代码啊!复制粘贴工程师的自我修养告诉我不能这样,这些活还是让别人干比较好ヽ(✿゚▽゚)ノ

于是就开始了新的复制之旅

开始

先来复制一段,新建个项目,项目根目录下npm init -y-y就是全yes了,至于yes了啥胸弟们可以不-y试试 ︿( ̄︶ ̄)︿

项目目录下多了个package.json,里面记录了项目相关的信息。

基于Webpack我们当然要安装Webpack了

npm i webpack webpack-cli -D

命令完成后,项目中多了一个 node_modules文件夹,该文件夹是用来存放项目中安装的依赖包,以后项目依赖的包也都会在里面。

初始化项目目录,新建src、dist、config以及index.html(html里随便写点啥比如hello world等会打开了至少不是白的就行),还有src目录下的入口文件index.js(空的即可)

初始化工作基本就完成了 webpack打包默认入口为src/index.js, 默认打包模式为 --mode development, 打包模式总共有两种:

  • mode development(开发环境)
  • mode production(生产环境)

现在可以在package.json -> scripts 栏目 配置build的命令

{
  "name": "test-webpack-bundler",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --mode production"
  },
  下面太长省略了...
}

然后执行npm run build

看已经打包成功了! 长征才刚刚开始...

webpack配置工程师警告(๑•̀ㅂ•́)و✧

webpack配置之旅

开发环境

npm i webpack-dev-server html-webpack-plugin internal-ip -D 开发环境本地服务就靠webpack-dev-server了,生成html、自动插入js等就靠html-webpack-plugin了,局域网设备可通过IP访问就靠internal-ip 在config目录里新建一个webpack.dev.js

又到了复制粘贴的时候啦

const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const packageConfig = require("../package.json");
const internalIp = require('internal-ip') // 借助这个实现可用局域网IP访问

const devWebpackConfig = {
  mode: "development",
  devtool:'#source-map',
  devServer: {
    port: 9527, // 指定端口号; 默认 8080
    hot: true, // 热更新
    host: internalIp.v4.sync(), // 可通过局域网IP访问,也可以通过 localhost 访问
    open: true, // 启动本地服务后,自动打开页面
    overlay: true, // 编译器错误或警告时, 在浏览器中显示全屏覆盖; 默认false
    progress: true, // 是否将运行进度输出到控制台; 默认 false
    contentBase: path.resolve(__dirname, "dist"), // 告诉服务器从哪里提供内容。只有在你想要提供静态文件时才需要
    publicPath: "/",
    // 精简终端输出
    stats: {
      modules: false,
      children: false,
      chunks: false,
      chunkModules: false
    }
  },
  entry: ["./src/index.js"],
  plugins: [
    new HtmlWebpackPlugin({
      template: "index.html", // 指定模板html文件
      title: packageConfig.name, // html的title的值,这里我从package.json里取了
      inject: true, // 自动引入JS脚本的位置,默认值为 true
    })
  ]
};

module.exports = devWebpackConfig;

devtool详情看这里 现在在package.json -> scripts 中配置dev的命令

"dev": "webpack-dev-server --config config/webpack.dev.js --color --progress"

现在npm run dev吧 胸弟们熟悉的不要不要的了吧,走你


没问题我们继续,有问题···胸弟们百度一下吧

现在我们就参考Vue Cli生成的项目整理目录吧

  • assets 主要是图片啊 图标啊 字体啊 之类的
  • styles 就是css啦
  • utils 就是工具函数了 比如写(复制)了个防抖啊、节流啊、时间格式化啊之类的扔进去,用的时候导入即可

webpack只认识js,图标、字体、css等其他的就需要各种loader拿给webpack,它才认识。

先搞定css吧,顺便把sass和scss也搞了(less同理,找对应loader即可) 随便写几句意思一下

index.html

    <header class="flex-container header-wrapper">
      <h1 class="title">test-webpack4-bundler</h1>
      <div class="user-avatar-box">
        <img class="adaptive-img" src="./src/assets/uncle.jpg" alt="" />
      </div>
      <ul>
        <li class="list-item">1</li>
        <li class="list-item">2</li>
        <li class="list-item">3</li>
        <li class="list-item">4</li>
        <li class="list-item">5</li>
        <li class="list-item">6</li>
      </ul>
    </header>

src/index.js

import './styles/index.scss' // global css

src/styles/index.scss

@import "./header.scss";

.flex-container {
  display: flex;
  justify-content: center;
  align-items: center;
}

.adaptive-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

src/styles/header.scss

.header-wrapper {
  flex-direction: column;
  .title {
    color: rgb(65, 85, 28);
  }
  .user-avatar-box {
    width: 120px;
    height: 120px;
    overflow: hidden;
    border-radius: 50%;
  }
}

复制粘贴

npm i css-loader style-loader sass-loader sass postcss-loader autoprefixer -D

装一堆 看名字也知道大概是干什么的了吧 postcss-loader配合autoprefixer就可以自动加-webkit这些前缀了

(当然PostCSS还能干很多事,想要了解的话点我

config/webpack.dev.js里,和plugins同级配置loader

  module: {
    rules: [
      {
        test: /\.(sa|sc|c)ss$/,
        use: [
          "style-loader",
          "css-loader",
          "postcss-loader",
          {
            loader: "sass-loader",
            options: {
              implementation: require("sass")
              // 默认使用的node-sass,这样配置就会使用dart-sass
            }
          }
          // webpack的规定,多个loader要倒着写,比如scss文件先给sass-loader解析成css再给css-loader,以此类推
        ]
      }
    ]
  }

项目根目录下创建两个文件用来配置postcss-loader和autoprefixer

.browserslistrc

> 1%
last 2 versions
not ie <= 9

postcss.config.js

module.exports = {
  plugins: {
    autoprefixer: {}
  }
};

又可以npm run dev了 已经看到我们想要的样子并且已经自动加了针对不同内核的前缀


css部分已经结束了,现在我们写点牛逼的代码吧

utils/index.js

export function $(selector) {
  return document.querySelector(selector);
}

src/index.js

import "./styles/index.scss"; // global css
import { $ } from "./utils";
import { resolve } from "path";

window.onload = () => {
  const showText = "守护姨父的微笑";
  setTimeout(() => {

  }, 1000);
  const changeTitle = () => {
    let myPromise = new Promise((resolve, reject) => {
      resolve();
    });
    return myPromise;
  };
  changeTitle().then(()=>{
    $(".title").innerHTML = `我们要${showText}`;
    const lists = [...document.querySelectorAll(".list-item")];
    lists.forEach(element => {
      console.log(element);
    });
    let [a, b, c] = ["索尼好!退果报平安", 2, 3];
    console.log(a);
    $(".title").innerHTML = `我们要${showText}${a}`;
  })
};

好了我们写了箭头函数、模板字符串、const声明、Promise都是es6的语法,某些不现代的浏览器不支持,所以我们需要Babel老弟帮帮我们

复制粘贴

npm i babel-loader @babel/core @babel/preset-env @babel/runtime @babel/plugin-transform-runtime @babel/plugin-syntax-dynamic-import -D

src/index.js 头部引入垫片

import "@babel/polyfill";

项目根目录下新建.babelrc

{
  "presets": ["@babel/preset-env"],
  "plugins": [
    "@babel/plugin-transform-runtime"
  ]
}

config/webpack.dev.js 里module>rules下增加一个loader

      {
        test: /\.js$/,
        use: ["babel-loader"],
        exclude: /node_modules/
      },

config/webpack.dev.js entry加入 @babel/polyfill

entry: ["@babel/polyfill","./src/index.js"]

package.json -> scripts build

"build": "webpack --config config/webpack.dev.js --mode production --color --progress"

npm run build走一波

es6的语法都没有了,该有的垫片也有了

(babel-polyfill和babel-runtime的关系和区别大概可以看这里)

好像大概是弄完了?等等,打包出来的html文件图片引用路径好像不对,没hash值迭代了缓存不得搞死我们啊,那么下一轮复制粘贴又要开始了。


生产环境

开发环境大概就这么样了吧,针对生产环境我们需要再搞点东西了 先复制一份webpack.dev.js,叫webpack.prod.js,作为生产环境的webpack配置

装,分别是解决js和html里的文件路径问题

npm i url-loader file-loader html-withimg-loader -D

然后删除开发服务器,

config/webpack.prod.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const packageConfig = require("../package.json");

const prodWebpackConfig = {
  mode: "production",
  devtool: false,
  entry: ["@babel/polyfill", "./src/index.js"],
  output: {
    path: path.resolve(__dirname, "../dist"),
    filename: path.posix.join("static", "js/[name].[chunkhash].js"),
    chunkFilename: path.posix.join("static", "js/[id].[chunkhash].js")
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: ["babel-loader"],
        exclude: /node_modules/
      },
      {
        test: /\.(sa|sc|c)ss$/,
        use: [
          "style-loader",
          "css-loader",
          "postcss-loader",
          {
            loader: "sass-loader",
            options: {
              implementation: require("sass")
              // 默认使用的node-sass,这样配置就会使用dart-sass
            }
          }
          // webpack的规定,多个loader要倒着写,比如scss文件先给sass-loader解析成css再给css-loader,以此类推
        ]
      },
      {
        test: /\.(htm|html)$/,
        loader: "html-withimg-loader"
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: "url-loader",
        options: {
          limit: 10000,
          name: path.posix.join("static", "img/[name].[hash:7].[ext]")
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: "url-loader",
        options: {
          limit: 10000,
          name: path.posix.join("static", "fonts/[name].[hash:7].[ext]")
        }
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "index.html", // 指定模板html文件
      title: packageConfig.name, // html的title的值,这里我从package.json里取了
      inject: true, // 自动引入JS脚本的位置,默认值为 true
      minify: {
        minifycss: true, // 压缩css
        minifyJS: true, // 压缩JS
        removeComments: true, // 去掉注释
        collapseWhitespace: true, // 去掉空行
        removeRedundantAttributes: true, // 去掉多余的属性
        removeAttributeQuotes:true, // 删除不需要引号的属性值
        removeEmptyAttributes: true // 去掉空属性
      }
    })
  ]
};

module.exports = prodWebpackConfig;

package.json -> scripts build 该用生产环境的配置build了

"build": "webpack --config config/webpack.prod.js --mode production --color --progress"

npm run build走一波

可以看到,html压缩过了,img路径也正确了,同时目录结构也整齐多了,文件也带了hash值

结尾

大体上完成了,后续还有一些优化,比如用指定的插件去压缩JS,CSS抽离成单个文件并优化,将根目录下的static下静态资源copy到打包目录下等,单独抽离第三方库等,就不一一说了,主要写这玩意太累了。。。大佬们好牛逼。。。。真的

如果有哪里有问题欢迎大佬们告诉我!感恩!

附上这个项目的地址 里面包含了比较完整的配置,至少我的项目里大概就是这样了。