meri-design中webpack 4架构

meri-design npm 传送门
webpack

唠两句

  • 现代的前端开发可以说每个公司都会用到打包工具,尤为webpack最香;webpack上市时候相继打败了grunt、gulp,后来有个号称‘零’配置的parcel上市,但它还是没有打败webpack,由此可以看出webpack是真的很香;
  • 有人说我用cli,基本不会去碰webpack,也对哈,不过我想说,你想要达到高级的效果,你就得碰碰webpack;
  • 我自己有个习惯,用别人造的轮子,还不如自己造一个,这样自己才能学到更多的知识,网上有个笑话,“工作三年,经验五年——怎么来的?加班来的”,其实是他是自己造来的!

依赖项:

  • cross-env 设置/获取全局变量
const path = require('path');
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const WebpackBar = require('webpackbar');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
  • MiniCssExtractPlugin 文本分离插件,分离.vue文件中的css
  • CleanWebpackPlugin 清理垃圾文件,打包前清理上次打包后的记录
  • WebpackBar 启动的时候打印一个进度条
  • VueLoaderPlugin vue加载器

接下来咱们来看一下webpack配置中的常用参数:

一、entry

  1. 入口
    入口有三种写法 String Array Object
  2. 在meri-design中的多组件打包是将所有组件的js打包到一个js、css文件中,在开发者使用过程中以达到全局引入的效果
  entry: {
    index: './src/entry/multiple.js', // 入口文件
  }
  1. multiple.js
// 引入组件并包装成key-value对象
import Button from '../components/Button';
const Components = {
  Button
}
// 安装注册组件
const install = (Vue) => Object.keys(Components).forEach((name) => {
  Vue.component(name, Components[name]);
});
// 支持使用标签的方式引入
if (typeof window !== 'undefined' && window.Vue) {
  install(window.Vue);
}

Components.install = install;
export default Components;

二、output

  1. 出口
    常用的有三个参数:
    • path 输出到的文件夹
      例:我要打包输出到根目录的dist文件夹下 path: path.resolve(__dirname, 'dist')
    • filename 输出到文件夹里保存的文件名
      例:我想要打包文件的名字为bundle.js filename: 'bundle.js',一般都用开发者开发中的名字 filename: '[name].js'
    • publishPath 页面引用的路径前缀
      一般都是相对路径和绝对路径的区别,不过有时候你想把静态资源放到其他服务器可以这样写 publishPath: 'http://www.xxx.com/public/javascript'
  2. 在meri-design中为了发布成npm包以及遵循require、import导入和标签引入做了以下配置:
output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'index.js',
    publicPath: '/',
    library: 'meri-design',
    libraryTarget: 'umd',
    umdNamedDefine: true,
    globalObject: 'this'
}
  • filename: 'index.js' 输出到dist目录下的js叫index.js
  • library: 'meri-design' 输出的包名叫meri-design,也就是require、import导入的模块名
  • libraryTarget: 'umd' libraryTarget会生成不同umd的代码,可以只是commonjs标准的,也可以是指amd标准的,也可以只是通过script标签引入的
  • umdNamedDefine: true 会对 UMD 的构建过程中的 AMD 模块进行命名,否则就使用匿名的 define
  • globalObject: 'this' 全局对象指定为this,不然会找不到window对象

三、module

  1. 解析模块
    使用webpack打包各种资源都需要相应的loader(加载器)去处理(解析、查找),如解析css:
  • 解析css需要 style-loader css-loader
  • 解析css预编译器less需要 style-loader css-loader less-loader
  • 解析.vue文件中的less需要 vue-style-loader css-loader less-loader,其中 vue-style-loader 是vue官方出的,而非webpack官方
    例在vue中解析less:
module: {
  rules: [
    {
      test: /\.css/,
      use: ['style-loader', 'css-loader']
    },
    {
      test: /\.less/,
      use: ['vue-style-loader', 'css-loader', 'less-loader']
    }
  ]
}
  1. 在meri-design中使用的是stylus
  • 设置css(stylus)公共变量
const cssConfig = [
  MiniCssExtractPlugin.loader,
  {
    loader: 'css-loader',
    options: {
      importLoaders: 1
    },
  },
  {loader: "postcss-loader"}
];
const stylusConfig = [
  MiniCssExtractPlugin.loader,
  'thread-loader',
  {
    loader: 'css-loader',
    options: {
      importLoaders: 2
    },
  },
  {loader: "postcss-loader"},
  {
    loader: 'stylus-loader'
  },
  {
    loader: 'style-resources-loader',
    options: {
      injector: 'prepend',
      patterns: path.resolve(__dirname, 'src/assets/stylus/variables.styl'),
    }
  }
];
  • module配置
module: {
    rules: [
      {
        test: /\.css$/,
        use: cssConfig,
      },
      {
        test: /\.styl(us)?$/,
        use: stylusConfig,
        include: [path.resolve(__dirname, 'src')],
      },
      {
        test: /\.vue$/,
        use: [
          'thread-loader',
          {
            loader: 'vue-loader',
            options: {
              loaders: {
                css: cssConfig,
                stylus: stylusConfig,
              },
              preserveWhitespace: false, // 不要留空白
            },
          }],
        include: [path.resolve(__dirname, 'src')],
      },
      {
        test: /\.js$/,
        use: ['thread-loader', 'babel-loader'],
        exclude: (file) => (
          /node_modules/.test(file) && !/\.vue\.js/.test(file)
        ),
      },
      {
        test: /\.svg$/,
        use: ['babel-loader', 'vue-svg-loader'],
        include: [path.resolve(__dirname, 'src')],
      },
      {
        test: /\.(png|jpe?g|gif|bmp)$/,
        use: [{
          loader: 'url-loader',
          options: { // 配置图片编译路径
            limit: 8192, // 小于8k将图片转换成base64
            name: '[name].[ext]?[hash:8]',
            outputPath: 'images/',
          },
        }],
        include: [path.resolve(__dirname, 'src')],
      },
      {
        test: /\.html$/,
        use: [{
          loader: 'html-loader',
          options: { // 配置html中图片编译
            minimize: true,
          }
        }]
      }
    ]
  }
}
  • 这里需特别注意的是 style-resources-loader,他是在stylus注入之前读取全局变量的插件,没有它不能获取到全局变量
  • importLoaders 可选值【0 1 2】
  • thread-loader 自动分配进程
  • MiniCssExtractPlugin.loader 分离css
  • babel-loader 使用babel处理js
    面试题:url-loader与file-loader有啥区别?

四、resolve

  • 词典翻译过来是解决,他要解决什么呢?要解决的是资源路径问题
resolve: { // 配置路径别名
    extensions: ['.js', '.vue', '.styl'],
    modules: [
      'node_modules',
      path.resolve(__dirname, 'src/assets'),
      path.resolve(__dirname, 'src/components'),
      path.resolve(__dirname, 'src/utils'),
    ]
  }
  • extensions import引入文件的时候不用加后缀
  • modules 别名配置
    面试题:在开发中怎么解决../、../../、../../../...?

五、plugins

  • 插件
plugins: [
    new webpack.BannerPlugin(`@meri-design ${TimeFn()}`),
    new VueLoaderPlugin(), // vue加载器
    new WebpackBar(),
    // new CleanWebpackPlugin([path.join(__dirname, 'dist'), path.join(__dirname, 'lib')]),
    new CleanWebpackPlugin(),
    new MiniCssExtractPlugin({ // 分离css
      filename: '[name].css',
    })
  ]
  • BannerPlugin webpack的内置插件,在打包输出的js的行头加上公共注释

六、devtool

  • 开发所依赖的工具
    devtool = 'source-map' 设置生成.map文件方便开发调试
    • webpack官网截图


      meri-design中webpack 4架构_第1张图片
      image.png

七、devServer

devServer = {
    contentBase: path.join(__dirname, 'production'), // 将 dist 目录下的文件,作为可访问文件。
    compress: true, // 开启Gzip压缩
    // , host: 'localhost' // 设置服务器的ip地址,默认localhost
    host: get_ip, // 设置服务器的ip地址,默认localhost
    port: 3002, // 端口号
    open: true, // 自动打开浏览器
    hot: true,
    overlay: { // 当出现编译器错误或警告时,就在网页上显示一层黑色的背景层和错误信息
      errors: true,
    },
    disableHostCheck: true, //  不检查主机
    // ,historyApiFallback: { // 当使用 HTML5 History API 时,任意的 404 响应都可能需要被替代为 /
    //     rewrites: [{ from: /./, to: '/' }]
    // }
  }
  • 开发所需的服务

八、optimization

  • 最佳化,最优化——优化代码
optimization = { // 抽离第三方插件
    splitChunks: {
      chunks: 'all', // 必须三选一: "initial" | "all" | "async"(默认就是异步)
      minSize: 10000, // 提高缓存利用率,这需要在http2/spdy
      maxSize: 0, // 没有限制
      minChunks: 3, // 共享最少的chunk数,使用次数超过这个值才会被提取
      maxAsyncRequests: 5, // 最多的异步chunk数
      maxInitialRequests: 5, // 最多的同步chunks数
      name: true,
      cacheGroups: { // 这里开始设置缓存的 chunks
        vendor: { // key 为entry中定义的 入口名称,new webpack.ProvidePlugin中的库
          test: /node_modules/, // 正则规则验证,如果符合就提取 chunk (指定是node_modules下的第三方包)
          name: 'vendor', // 要缓存的 分隔出来的 chunk 名称
          enforce: true,
        },
        styles: {
          test: /src\.(css|styl)$/,
          name: 'main',
          enforce: true,
        },
      },
    },
    runtimeChunk: { name: 'manifest' }, // 为每个入口提取出webpack runtime模块
  }

九、完整配置

const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 自动生成index.html
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); // 文本分离插件,分离js和css
const CleanWebpackPlugin = require('clean-webpack-plugin'); // 清理垃圾文件

const WebpackBar = require('webpackbar');

const VueLoaderPlugin = require('vue-loader/lib/plugin'); // vue加载器

const HardSourceWebpackPlugin = require('hard-source-webpack-plugin'); // 为模块提供中间缓存,缓存默认的存放路径是: node_modules/.cache/hard-source

/**
 * 判断是生产环境还是开发环境
 * @type {boolean}
 * isProd为true表示生产
 */
const isProd = process.env.NODE_ENV === 'production';

// 获取本机ip
const get_ip = require('./get_ip')();

// 获取时间
const TimeFn = require('./get_time');

/**
 *  css和stylus开发、生产依赖
 *  生产分离css
 */
const cssConfig = [
  isProd ? MiniCssExtractPlugin.loader : 'vue-style-loader',
  'thread-loader',
  {
    loader: 'css-loader',
    options: {
      sourceMap: !isProd,
    },
  },
  'postcss-loader',
];
const stylusConfig = [
  isProd ? MiniCssExtractPlugin.loader : 'vue-style-loader',
  'thread-loader',
  {
    loader: 'css-loader',
    options: {
      sourceMap: !isProd,
    },
  },
  {
    loader: 'stylus-loader',
    options: {
      sourceMap: !isProd,
    },
  }, {
    loader: 'style-resources-loader',
    options: {
      injector: 'prepend',
      patterns: path.resolve(__dirname, 'src/assets/stylus/variables.styl'),
    },
  },
];

const config = {
  entry: {
    index: isProd ? ['core-js/stable', 'regenerator-runtime/runtime', './src/main.js'] : './src/main.js' // 入口文件
    // index: ['core-js/stable', 'regenerator-runtime/runtime', './src/main.js'], // 入口文件
  },
  output: {
    path: path.resolve(__dirname, 'production'),
    filename: isProd ? 'javascript/[name].[hash:8].js' : '[name].js', // [name] 是entry的key
    publicPath: isProd ? './' : '/',
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: cssConfig,
      },
      {
        test: /\.styl(us)?$/,
        use: stylusConfig,
        include: [path.resolve(__dirname, 'src')],
      },
      {
        test: /\.vue$/,
        use: [
          'thread-loader',
          {
            loader: 'vue-loader',
            options: {
              loaders: {
                css: cssConfig,
                stylus: stylusConfig,
              },
              preserveWhitespace: false, // 不要留空白
            },
          }],
        include: [path.resolve(__dirname, 'src')],
      },
      {
        test: /\.js$/,
        use: ['thread-loader', 'cache-loader', `babel-loader?cacheDirectory=${!isProd}`],
        exclude: (file) => (
          /node_modules/.test(file) && !/\.vue\.js/.test(file)
        ),
      },
      {
        test: /\.svg$/,
        use: ['thread-loader', 'babel-loader', 'vue-svg-loader'],
        include: [path.resolve(__dirname, 'src')],
      },
      {
        test: /\.(png|jpe?g|gif|bmp)$/,
        use: [
          {
            loader: 'url-loader',
            options: { // 配置图片编译路径
              limit: 8192, // 小于8k将图片转换成base64
              name: '[name].[ext]?[hash:8]',
              outputPath: 'images/',
            },
          }],
        include: [path.resolve(__dirname, 'src')],
      },
      {
        test: /\.html$/,
        use: [{
          loader: 'html-loader',
          options: { // 配置html中图片编译
            minimize: true,
          },
        }],
      }
    ]
  },
  resolve: { // 配置路径别名
    extensions: ['.js', '.vue', '.styl'], // import引入文件的时候不用加后缀
    modules: [
      'node_modules',
      path.resolve(__dirname, 'src/assets'),
      path.resolve(__dirname, 'src/docs'),
      path.resolve(__dirname, 'src/utils'),
    ],
  },
  plugins: [
    new webpack.BannerPlugin(`@meri-design ${TimeFn()}`),
    new HardSourceWebpackPlugin(),
    new VueLoaderPlugin(), // vue加载器
    new HtmlWebpackPlugin({
      template: path.join(__dirname, 'src/index.html'), // 引入模版
      favicon: path.join(__dirname, 'src/assets/favicon.ico'),
      filename: 'index.html',
      minify: { // 对index.html压缩
        collapseWhitespace: isProd, // 去掉index.html的空格
        removeAttributeQuotes: isProd, // 去掉引号
      },
      hash: true, // 去掉上次浏览器的缓存(使浏览器每次获取到的是最新的html)
      // ,chunks:['vendor','main'] // 在产出的html文件里面引入哪些代码块,里面的名字要跟entry里面key对应(一般用于多文件入口)
      inlineSource: '.(js|css)',
    }),
    new WebpackBar(),
  ],
};

if (isProd) {
  config.plugins.push(
    new CleanWebpackPlugin([path.join(__dirname, 'production')]),
    new MiniCssExtractPlugin({ // 分离css
      filename: 'stylesheets/[name].[contenthash:8].css',
      // chunkFilename: isProd?'stylesheets/[name].[contenthash:8].css':'[name].css'
    })
  );

  config.optimization = { // 抽离第三方插件
    splitChunks: {
      chunks: 'all', // 必须三选一: "initial" | "all" | "async"(默认就是异步)
      minSize: 10000, // 提高缓存利用率,这需要在http2/spdy
      maxSize: 0, // 没有限制
      minChunks: 3, // 共享最少的chunk数,使用次数超过这个值才会被提取
      maxAsyncRequests: 5, // 最多的异步chunk数
      maxInitialRequests: 5, // 最多的同步chunks数
      name: true,
      cacheGroups: { // 这里开始设置缓存的 chunks
        vendor: { // key 为entry中定义的 入口名称,new webpack.ProvidePlugin中的库
          test: /node_modules/, // 正则规则验证,如果符合就提取 chunk (指定是node_modules下的第三方包)
          name: 'vendor', // 要缓存的 分隔出来的 chunk 名称
          enforce: true,
        },
        styles: {
          test: /src\.(css|styl)$/,
          name: 'main',
          enforce: true,
        },
      },
    },
    runtimeChunk: { name: 'manifest' }, // 为每个入口提取出webpack runtime模块
  };
} else {
  config.devtool = 'source-map'; // 如果只用source-map开发环境出现错误定位源文件,生产环境会生成map文件
  config.devServer = {
    contentBase: path.join(__dirname, 'production'), // 将 dist 目录下的文件,作为可访问文件。
    compress: true, // 开启Gzip压缩
    // , host: 'localhost' // 设置服务器的ip地址,默认localhost
    host: get_ip, // 设置服务器的ip地址,默认localhost
    port: 3002, // 端口号
    open: true, // 自动打开浏览器
    hot: true,
    overlay: { // 当出现编译器错误或警告时,就在网页上显示一层黑色的背景层和错误信息
      errors: true,
    },
    disableHostCheck: true, //  不检查主机
    // ,historyApiFallback: { // 当使用 HTML5 History API 时,任意的 404 响应都可能需要被替代为 /
    //     rewrites: [{ from: /./, to: '/' }]
    // }
  };
}

module.exports = config;

你可能感兴趣的:(meri-design中webpack 4架构)